Authored by 姜枫

merge from develop

Showing 63 changed files with 3833 additions and 252 deletions

Too many changes to show.

To preserve performance only 63 of 63+ files are displayed.

bundle/**/*.js
dist/**/*.js
\ No newline at end of file
**/bundle/**/*.js
**/dist/**/*.js
coverage
... ...
... ... @@ -132,8 +132,7 @@ Session.vim
*~
# auto-generated tag files
tags
### VS Code ###
.vscode/
... ...
... ... @@ -17,17 +17,15 @@ const path = require('path');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const favicon = require('serve-favicon');
const session = require('yoho-express-session');
const memcached = require('yoho-connect-memcached');
const uuid = require('uuid');
const _ = require('lodash');
const yohoLib = require('yoho-node-lib');
const session = require('express-session');
const memcached = require('connect-memcached');
const hbs = require('express-handlebars');
const pkg = require('./package.json');
const app = express();
const MemcachedStore = memcached(session);
// 向模板注入变量
app.locals.devEnv = app.get('env') === 'development';
app.locals.version = pkg.version;
... ... @@ -35,47 +33,79 @@ app.locals.version = pkg.version;
// 全局注册library
yohoLib.global(config);
// 指定libray目录
global.middleware = path.resolve('./doraemon/middleware');
global.utils = path.resolve('./utils');
const logger = global.yoho.logger;
app.set('view engine', '.hbs');
app.set('views', './doraemon/views');
app.engine('.hbs', hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: './doraemon/views',
partialsDir: './doraemon/views/partial',
helpers: global.yoho.helpers
}));
app.use(favicon(path.join(__dirname, '/public/favicon.ico')));
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(cookieParser());
app.use(session({
proxy: true,
resave: false,
saveUninitialized: true,
unset: 'destroy',
secret: 'nothing', // 兼容 PHP SESSION,sessionID 不加密
name: 'PHPSESSID', // 兼容 PHP SESSION
genid: () => {
return uuid.v4(); // 兼容 PHP SESSION
},
secret: '82dd7e724f2c6870472c89dfa43cf48d',
name: 'yohobuy_session',
cookie: {
domain: 'yohobuy.com'
domain: 'yohobuy.com',
httpOnly: false
},
store: new MemcachedStore({
hosts: config.memcache.session,
prefix: 'qinsessionsession:', // 兼容 PHP SESSION
key: 'yohobuy_session' // 兼容 PHP SESSION
prefix: 'yohobuy_session:'
})
}));
app.use((req, res, next) => {
req.user = {};
req.user = {}; // 全局的用户数据
req.yoho = {}; // req和res绑定yoho对象,用于传递全局数据, 如req.yoho.channel等
// 从 PHP 写的 SESSION 中获取到当前登录用户的 UID
if (req.session && _.isNumber(req.session._LOGIN_UID)) {
req.user.uid = req.session._LOGIN_UID;
}
next();
});
// dispatcher
require('./dispatch')(app);
try {
const user = require('./doraemon/middleware/user');
const setYohoData = require('./doraemon/middleware/set-yoho-data');
const errorHanlder = require('./doraemon/middleware/error-handler');
const setPageInfo = require('./doraemon/middleware/set-pageinfo');
// YOHO 前置中间件
app.use(setYohoData());
app.use(user());
app.use(setPageInfo());
require('./dispatch')(app);
app.all('*', errorHanlder.notFound()); // 404
// YOHO 后置中间件
app.use(errorHanlder.serverError());
} catch (err) {
console.error(err);
logger.error(err);
}
// listener
app.listen(config.port, function() {
console.log('yohobuy start');
logger.info('yohobuy start');
});
... ...
'use strict';
const mRoot = '../models';
const library = '../../../library';
const couponModel = require(`${mRoot}/coupon`); // 领取优惠券 model
const log = require(`${library}/logger`);
exports.index = (req, res) => {
var renderData = {
module: 'coupon',
page: 'index',
appDownLoadLink: 'http://a.app.qq.com/o/simple.jsp?pkgname=com.yoho'
},
ordercode = req.query.ordercode,
uid = req.query.uid,
mobile = req.query.mobile;
renderData.uid = uid;
renderData.mobile = mobile;
renderData.ordercode = ordercode;
// 测试假数据
// res.render('coupon', renderData); // 渲染页面
couponModel.getPageInfo({
ordercode: ordercode,
uid: uid
}).then((couponData) => {
if (couponData.code === 200) {
// 获取信息成功
couponData.ordercode = ordercode;
}
couponData.appDownLoadLink = renderData.appDownLoadLink;
res.render('coupon', {
result: couponData,
module: 'coupon',
page: 'index',
title: couponData.title
});
}).catch((err) => {
log.error('频道页面渲染错误:' + JSON.stringify(err));
});
};
exports.getCoupon = (req, res) => {
var renderData = {
module: 'coupon',
page: 'index'
};
var ordercode = req.query.ordercode,
mobile = req.query.mobile;
renderData.mobile = mobile;
renderData.ordercode = ordercode;
// //测试假数据
// res.render('coupon', renderData); // 渲染页面
couponModel.getCoupon({
ordercode: ordercode,
mobile: mobile
}).then((couponData) => {
if (couponData.code === 200) {
// 获取信息成功
couponData.ordercode = ordercode;
}
console.log(couponData);
res.json({
result: couponData
});
}).catch((err) => {
log.error('频道页面渲染错误:' + JSON.stringify(err));
});
};
exports.verify = (req, res) => {
var renderData = {
module: 'coupon',
page: 'index'
};
var ordercode = req.query.ordercode,
mobile = req.query.mobile,
identifycode = req.query.identifycode;
renderData.mobile = mobile;
renderData.ordercode = ordercode;
renderData.identifycode = identifycode;
// //测试假数据
// res.render('coupon', renderData); // 渲染页面
couponModel.registerAndSendCoupon({
ordercode: ordercode,
mobile: mobile,
identifycode: identifycode
}).then((couponData) => {
if (couponData.code === 200) {
// 获取信息成功
couponData.ordercode = ordercode;
}
console.log(couponData);
res.json({
result: couponData
});
}).catch((err) => {
log.error('频道页面渲染错误:' + JSON.stringify(err));
});
};
... ...
'use strict';
const wechatModel = require('../models/wechat');
exports.wechatShare = (req, res, next) => {
wechatModel.calcSignature({
url: req.query.url || 'http://www.yohobuy.com/'
}).then((result) => {
res.jsonp(result);
}).catch(next);
};
... ...
/**
* sub app coupon
* @author: lixia.zhang<lixia.zhang@yoho.cn>
* @date: 2016/05/31
*/
var express = require('express'),
path = require('path'),
hbs = require('express-handlebars');
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.set('views', path.join(__dirname, 'views/action'));
app.engine('.hbs', hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: ['./views/partial', `${doraemon}/partial`],
helpers: global.yoho.helpers
}));
// router
app.use(require('./router'));
module.exports = app;
... ...
var API = require('../../../library/api').API;
var api = new API();
const library = '../../../library';
const sign = require(`${library}/sign`);
/**
* 分享页面基础参数
* @param {object} sizeInfo [接口原始数据]
* @return {object} [description]
*/
const getPageInfo = (pageInfo) => {
var dest = {};
dest.shareTitle = pageInfo.data.shareTitle;
dest.shareDesc = pageInfo.data.shareContent;
dest.shareImg = pageInfo.data.shareImgUrl;
dest.shareLink = pageInfo.data.shareUrl;
dest.code = pageInfo.code;
dest.activityID = pageInfo.id;
dest.title = pageInfo.data.h5Title;
dest.activityDesc = pageInfo.data.activityDesc;
dest.couponPic = pageInfo.data.couponPic;
dest.oldUserCouponPic = pageInfo.data.oldUserCouponPic;
dest.mobile = pageInfo.data.mobile;
// 强制活动开始,活动上线产品要求这样设置
pageInfo.data.flag = 1;
if (pageInfo.data.flag === 1) {
dest.bgImg = pageInfo.data.activityNormalPic;
} else {
dest.bgImg = pageInfo.data.activityEndPic;
dest.ended = true;
}
dest.message = pageInfo.data.returnMsg;
// 清空变量,释放内存
pageInfo = {};
return dest;
};
const getUserStatus = (param) => {
var dest = {};
dest.code = param.code;
dest.title = param.data.h5Title;
dest.returnCode = param.data.returnCode;
dest.mobile = param.data.mobile;
if (param.data.returnCode === 0) {
dest.geted = true;
} else if (param.data.returnCode === 1) {
dest.wrongNumb = true;
} else if (param.data.returnCode === 2) {
dest.newUser = true;
} else if (param.data.returnCode === 8) {
dest.oldUserAskCouponOnceMore = true;
} else if (param.data.returnCode === 5) {
dest.vertifyWrong = true;
} else {
dest.ended = true;
}
if (param.data.newUser === 1) {
dest.newUser = true;
}
dest.message = param.data.returnMsg;
// 清空变量,释放内存
param = {};
return dest;
};
/**
* 获取分享页面数据
*/
exports.getPageInfo = (data) => {
var defaultParam = {
method: 'app.activity.getInfoOfOrderShare'
},
// 处理完成后,发给后端
infoData = Object.assign(defaultParam, data);
return api.get('', sign.apiSign(infoData)).then(result => {
return getPageInfo(result);
}); // 所有数据返回一个 Promise,方便 Promise.all 调用
};
/**
* 输入手机号领券,新用户返回验证码
*/
exports.getCoupon = (data) => {
var defaultParam = {
method: 'wap.order.drawOrderShareCoupon'
},
// 处理完成后,发给后端
phoneData = Object.assign(defaultParam, data);
return api.get('', sign.apiSign(phoneData)).then(result => {
return getUserStatus(result);
}); // 所有数据返回一个 Promise,方便 Promise.all 调用
};
/**
* 验证注册码进行注册并发券
*/
exports.registerAndSendCoupon = (data) => {
var defaultParam = {
method: 'wap.order.registerAndSendCoupon'
},
// 处理完成后,发给后端
verifyData = Object.assign(defaultParam, data);
return api.get('', sign.apiSign(verifyData)).then(result => {
return getUserStatus(result);
}); // 所有数据返回一个 Promise,方便 Promise.all 调用
};
... ...
'use strict';
/*
* 生成微信分享所需的签名
* bikai <kai.bi@yoho.cn>
* 2016.6.15
*/
const request = require('request-promise');
const Promise = require('bluebird');
const crypto = require('crypto');
const logger = require('../../../library/logger');
const cache = require('../../../library/cache');
// 此处请勿使用有货公众号的 appId, 此处使用的是 女生志 的appId
const appId = 'wxb52ec6a352f0b090';
const secret = '9fe6bedb0b7f30986a168c7fc44f34c0';
const sha1 = (str) => {
const generator = crypto.createHash('sha1');
generator.update(str);
return generator.digest('hex');
};
const accessTokenCacheKey = 'wechatShare:accessToken';
const ticketCacheKey = 'wechatShare:ticket';
// 微信 JS 接口签名校验工具 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
const wechat = {
getAccessToken: Promise.coroutine(function* () {
let accessToken = yield cache.get(accessTokenCacheKey);
if (accessToken) {
return accessToken;
}
logger.info('调用微信 API 获取 accessToken');
return request({
url: 'https://api.weixin.qq.com/cgi-bin/token',
qs: {
grant_type: 'client_credential',
appid: appId,
secret: secret
},
json: true
}).then((res) => {
// accessToken 有效期 7200s,缓存 7100s
cache.set(accessTokenCacheKey, res.access_token, 7100).catch((err) => {
logger.error('微信分享 Token, 缓存 accessToken 时出错', JSON.stringify(err));
});
return res.access_token;
}).catch((err) => {
logger.error('微信分享 Token, 获取 accessToken 时出错', JSON.stringify(err));
});
}),
getTicket: Promise.coroutine(function* () {
let ticket = yield cache.get(ticketCacheKey);
if (ticket) {
return ticket;
}
logger.info('调用微信 API 获取 ticket');
return request({
url: 'https://api.weixin.qq.com/cgi-bin/ticket/getticket',
qs: {
access_token: yield this.getAccessToken(),
type: 'jsapi'
},
json: true
}).then(res => {
// ticket 有效期 7200s,缓存 7100s
cache.set(ticketCacheKey, res.ticket, 7100).catch((err) => {
logger.error('微信分享 Token, 缓存 ticket 时出错', JSON.stringify(err));
});
return res.ticket;
}).catch((err) => {
logger.error('微信分享 Token, 获取 ticket 时出错', JSON.stringify(err));
});
}),
calcSignature: Promise.coroutine(function* (data) {
data = Object.assign({
nonceStr: Math.random().toString(36).substr(2, 15),
timestamp: Math.floor(Date.now() / 1000) + '',
ticket: yield this.getTicket(),
appId: appId
}, data);
const str = `jsapi_ticket=${data.ticket}&noncestr=${data.nonceStr}&timestamp=${data.timestamp}&url=${data.url}`;
data.signature = sha1(str);
return data;
})
};
// 测试
// wechat.calcSignature({
// url: 'http://www.yohobuy.com/'
// }).then(console.log);
module.exports = wechat;
... ...
/**
* router of sub app coupon
* @author: lixia.zhang<lixia.zhang@yoho.cn>
* @date: 2016/05/31
*/
'use strict';
const router = require('express').Router(); // eslint-disable-line
const cRoot = './controllers';
const coupon = require(`${cRoot}/coupon`);
const wechat = require(`${cRoot}/wechat`);
// routers
router.get('/coupon', coupon.index);
router.get('/coupon/phone', coupon.getCoupon);
router.get('/coupon/verify', coupon.verify);
router.get('/wechat/share', wechat.wechatShare);
module.exports = router;
... ...
{{# result}}
<div class="receive-coupon-page">
<div class="bg-contain">
<img src="{{image bgImg 640 1136}}">
</div>
<p class="hidden" id="orderCode">{{ordercode}}</p>
<p class="hidden" id="oldUserCouponPic">{{oldUserCouponPic}}</p>
<p class="hidden" id="newUserCouponPic">{{couponPic}}</p>
<p class="hidden" id="activityEnded">{{ended}}</p>
<p class="hidden" id="newUser">{{newUser}}</p>
<p class="hidden" id="tipMessage">{{message}}</p>
<p class="hidden" id="activityID">{{activityID}}</p>
<input type="hidden" id="shareTitle" value="{{shareTitle}}">
<input type="hidden" id="shareDesc" value="{{shareDesc}}">
<input type="hidden" id="shareImg" value="{{shareImg}}">
<input type="hidden" id="shareLink" value="{{shareLink}}">
<div class="page">
<div class="gain-coupon-centent hidden">
<div class="coupon">
<img src={{couponPic}}>
</div>
<p class="phone" id="mobile"></p>
<p>登录Yoho!Buy有货客户端即可使用</p>
<div class="use-coupon-btn">
<a href="{{appDownLoadLink}}">马上去Yoho!Buy有货使用</a>
</div>
<span class="description"></span>
</div>
<div class="coupon-centent">
<div class="title">
</div>
<div class="under-title">
<div class="input-content phone-input-content">
<input id="phone" type="text" placeholder="请输入手机号" maxlength="11"/>
<i class="clear-input iconfont hidden clear-mobile">&#xe626;</i>
<div>领取红包</div>
</div>
<div class="input-content vertify-input-content hidden">
<input id="verification" type="text" placeholder="请输入验证码" maxlength="4"/>
<i class="clear-input iconfont hidden clear-verify">&#xe626;</i>
<div>验证领红包</div>
</div>
<div class="coupon-description">
<span></span>
</div>
</div>
</div>
</div>
<div class="tip-wrap hidden">
<div class="tip fail">
<div class="header">
</div>
<div class="title">
{{message}}
<!-- 优惠券已领光 -->
</div>
<div class="logo">
</div>
</div>
</div>
<div class="messages hidden" id="dialog">
<div class="content"></div>
</div>
<div class="mask hidden"></div>
<div class="dialog hidden" id="message">
<span class="close"></span>
{{{activityDesc}}}
</div>
</div>
{{/result}}
... ...
... ... @@ -8,7 +8,6 @@ const _ = require('lodash');
const channelModel = require('../models/channel');
const helpers = global.yoho.helpers;
const log = global.yoho.logger;
const renderData = {
module: 'channel',
... ... @@ -27,11 +26,6 @@ const renderData = {
pageFooter: true
};
const channelLogger = (err, res) => {
log.error('频道页面渲染错误:' + JSON.stringify(err));
res.send('error');
};
/**
* 频道页生成函数
* @param {[object]} req
... ... @@ -40,20 +34,18 @@ const channelLogger = (err, res) => {
* @return {[type]}
*/
const channelPage = (req, res, data) => {
channelModel.getChannelData({
return channelModel.getChannelData({ // TODO 内部的Promise方法必须 return 出来
gender: data.gender,
uid: _.toString(req.user.uid)
}).then(result => {
res.render('channel', Object.assign({}, renderData, data, result));
}).catch((err) => {
channelLogger(err, res);
});
};
/**
* 频道选择页
*/
exports.index = (req, res) => {
exports.index = (req, res, next) => {
channelModel.getChannelSwitchData().then((result) => {
res.render('index', {
module: 'channel',
... ... @@ -61,10 +53,11 @@ exports.index = (req, res) => {
title: 'Yoho!Buy 有货',
searchUrl: helpers.urlFormat('/', null, 'search'),
pageFooter: true,
channelList: result[0],
channelList: result[0].channelList,
yohood: result[0].yohood,
background: result[1]
});
});
}).catch(next);
};
/**
... ... @@ -90,46 +83,46 @@ exports.switchChannel = (req, res, next) => {
/**
* 男生首页
*/
exports.boys = (req, res) => {
exports.boys = (req, res, next) => {
channelPage(req, res, {
gender: 'boys',
title: '男生首页',
boysHomePage: true
});
}).catch(next); // TODO 我们在路由处理的最上层的方法处理catch
};
/**
* 女生首页
*/
exports.girls = (req, res) => {
exports.girls = (req, res, next) => {
channelPage(req, res, {
gender: 'girls',
title: '女生首页',
girlsHomePage: true
});
}).catch(next);
};
/**
* 潮童首页
*/
exports.kids = (req, res) => {
exports.kids = (req, res, next) => {
channelPage(req, res, {
gender: 'kids',
title: '潮童首页',
kidsHomePage: true
});
}).catch(next);
};
/**
* 创意生活首页
*/
exports.lifestyle = (req, res) => {
exports.lifestyle = (req, res, next) => {
channelPage(req, res, {
gender: 'lifestyle',
title: '创意生活首页',
lifestyleHomePage: true
});
}).catch(next);
};
/**
... ... @@ -138,12 +131,10 @@ exports.lifestyle = (req, res) => {
* @param {[object]} res
* @return {[type]}
*/
exports.bottomBanner = (req, res) => {
exports.bottomBanner = (req, res, next) => {
let gender = req.query.gender || 'boys';
channelModel.getBottomBannerData(gender).then(result => {
res.send(result);
}).catch((err) => {
channelLogger(err, res);
});
}).catch(next);
};
... ...
... ... @@ -4,16 +4,15 @@
* @date: 2016/05/09
*/
'use strict';
const library = '../../../library';
const utils = '../../../utils';
const contentCodeConfig = require('../../../config/content-code');
const _ = require('lodash');
const ServiceAPI = require(`${library}/api`).ServiceAPI;
const sign = require(`${library}/sign`);
const camelCase = require(`${library}/camel-case`);
const logger = require(`${library}/logger`);
const api = global.yoho.ServiceAPI;
const sign = global.yoho.sign;
const camelCase = global.yoho.camelCase;
const logger = global.yoho.logger;
const resourcesProcess = require(`${utils}/resources-process`);
const api = new ServiceAPI();
/**
* 性别数据
... ... @@ -28,49 +27,37 @@ const genderData = {
* 楼层资源的位置码
* @type {Object}
*/
const contentCode = {
boys: '8512bf0755cc549ac323f852c9fd945d',
girls: '189b6686065dbd6755dd6906cf03c002',
kids: 'b8c1bff53d4ea60f978926d538620636',
lifestyle: '61cd852c6afcf60660196154f66a3a62',
index: '7ba9118028f9b22090b57341487567eb'
};
const contentCode = contentCodeConfig.channel;
/**
* 频道底部 Banner 位置码
* @type {Object}
*/
const bottomBannerCode = {
boys: 'a2ec977c027d0cd9cdccb356ddf16b08',
girls: '8c8bd1b89a22e5895f05882e0825b493'
};
const bottomBannerCode = contentCodeConfig.bottom;
/**
* 频道选择页 默认数据
* @type {Object}
*/
const channelList = {
1: {
const channelList = [
{
href: '/boys',
title: '男生',
entitle: 'BOYS'
},
2: {
}, {
href: '/girls',
title: '女生',
entitle: 'GIRLS'
},
3: {
}, {
href: '/kids',
title: '潮童',
entitle: 'KIDS'
},
4: {
}, {
href: '/lifestyle',
title: '创意生活',
entitle: 'LIFESTYLE'
}
};
];
/**
* 获取二级菜单顶部颜色
... ... @@ -181,18 +168,20 @@ const getChannelList = () => {
if (result && result.code === 200) {
const list = {};
list.channelList = [];
list.yohood = {};
result.data.list = camelCase(result.data.list || []);
_.forEach(result.data.list, function(item) {
const channel = channelList[item.channelId];
const channel = channelList[item.yhChannel - 1];
if (channel) {
list[item.channelId] = channelList[item.channelId];
list.channelList.push(channel);
}
if (_.toNumber(item.channelId) === 5) {
list.showYohood = true;
list.yohoodHref = 'http://yohood.cn';
if (_.toNumber(item.yhChannel) !== 5) {
list.yohood.showYohood = true;
list.yohood.yohoodHref = 'http://yohood.cn';
}
});
return Object.keys(list).length ? list : channelList;
... ...
... ... @@ -24,9 +24,11 @@
<span class="iconfont right-icon">&#xe614;</span>
</a>
{{/each}}
{{#showYohood}}
<a href="{{yohoodHref}}" id="yohood" class="list-item"> <span class="iconfont right-icon">&#xe614;</span></a>
{{/showYohood}}
{{#yohood}}
{{#if showYohood}}
<a href="{{yohoodHref}}" id="yohood" class="list-item"> <span class="iconfont right-icon">&#xe614;</span></a>
{{/if}}
{{/yohood}}
</div>
</div>
</div>
... ...
/**
* 新潮教室
* @author: wsl<shuiling.wang@yoho.cn>
* @date: 2016/05/30
*/
'use strict';
const mRoot = '../models';
const _ = require('lodash');
const starModel = require(`${mRoot}/star`);
// const headerModel = require('../../../doraemon/models/header');
const headTab = [
{
url: '/guang/star',
name: '全部'
},
{
url: '/guang/star/special',
name: '星专题'
},
{
url: '/guang/star/collocation?uid=',
name: '星搭配'
}
];
const processPublicData = (req, title) => {
// let headerData = headerModel.setNav({
// navTitle: title,
// navBtn: navBtn
// });
let renderData = {
module: 'guang',
title: title
// pageHeader: headerData
};
if (req.query.app_version || req.query.appVersion) {
renderData.pageHeader = false;
}
return renderData;
};
/**
* 星潮教室首页
* @param {[object]} req
* @param {[object]} res
* @return {[type]}
*/
exports.index = (req, res, next) => {
const pageHeadTab = _.cloneDeep(headTab);
pageHeadTab[0].cur = true;
pageHeadTab[2].url += (req.query.uid || 0);
// res.render('star/index', _.assign({
// page: 'star',
// isStarIndexPage: true,
// headTab: pageHeadTab
// }, processPublicData(req, '星潮教室')));
starModel.getIndexData((req.query.uid || 0)).then((result) => {
res.render('star/index', _.assign({
page: 'star',
isStarIndexPage: true,
headTab: pageHeadTab,
content: {
focus: true,
data: result.ads
},
starAvatar: result.starAvatar.concat(result.starAvatar).concat(result.starAvatar), // 需要优化,数组重复三次
articles: result.articles
}, processPublicData(req, '星潮教室')));
}).catch(next);
};
exports.getIndexHtml = (req, res, next) => {
starModel.getIndexData((req.query.uid || 0)).then((result) => {
res.render('star/index-html', _.assign({
layout: false,
content: {
focus: true,
data: result.ads
},
starAvatar: result.starAvatar.concat(result.starAvatar).concat(result.starAvatar), // 需要优化,数组重复三次
articles: result.articles
}));
}).catch(next);
};
/**
* 星专题
* @param {[object]} req
* @param {[object]} res
* @return {[type]}
*/
exports.special = (req, res, next) => {
starModel.getSpecialData().then((result) => {
const pageHeadTab = _.cloneDeep(headTab);
pageHeadTab[1].cur = true;
pageHeadTab[2].url += (req.query.uid || 0);
res.render('star/special', _.assign({
page: 'special'
}, processPublicData(req, '星潮教室'), {
resources: result,
headTab: pageHeadTab,
notIndex: true
}));
}).catch(next);
};
/**
* 星搭配
* @param {[object]} req
* @param {[object]} res
* @return {[type]}
*/
exports.collocation = (req, res) => {
const pageHeadTab = _.cloneDeep(headTab);
pageHeadTab[2].cur = true;
pageHeadTab[2].url += (req.query.uid || 0);
res.render('star/collocation', _.assign({
isStarDetailPage: true,
page: 'collocation'
}, processPublicData(req, '星潮教室'), {
headTab: pageHeadTab,
notIndex: true
}));
};
/**
* 星搭配文章请求
* @param {[object]} req
* @param {[object]} res
* @return {[type]}
*/
exports.collocationList = (req, res, next) => {
let params = req.query;
let uid = req.query.uid || 0; // 客户端访问,不能使用 cookie
starModel.getCollocationListData(params, uid).then((result) => {
res.render('star/list', _.assign({
layout: false,
params: params,
list: result,
isApp: req.query.app_version || req.query.appVersion || false
}));
}).catch(next);
};
/**
* 收藏文章
* @param {[type]} req [description]
* @param {[type]} res [description]
* @return {[type]}
*/
exports.setFavorite = (req, res, next) => {
let uid = req.body.uid || 0; // 客户端访问,不能使用 cookie
let urlEncode = '';
let url = '';
let params = {
articleId: req.body.articleId,
type: req.body.type
};
if (req.body.pageType === '1') {
url = `/guang/star/collocation?uid=${uid}`;
urlEncode = '\/guang\/star\/collocation';
} else {
url = `/guang/star/detail?tag=${req.body.tag}&uid=${uid}`;
urlEncode = '\/guang\/star\/detail';
}
starModel.setFavorite(params, uid).then((result) => {
if (result.code === 400) {
result.data = `${url}?openby:yohobuy={"action":"go.weblogin","params":{"jumpurl":{"url":"http:\/\/m.yohobuy.com${urlEncode}","param":{"from":"app"}},"requesturl":{"url":"","param":{}},"priority":"N"}}`; // eslint-disable-line
}
res.json(result);
}).catch(next);
};
/**
* 明星文章专区
* @param {[object]} req
* @param {[object]} res
* @return {[type]}
*/
exports.detail = (req, res) => {
res.render('star/detail', _.assign({
isStarDetailPage: true,
page: 'detail-list'
}, processPublicData(req, req.query.tag, false)));
};
/**
* 明星专题文章请求
* @param {[object]} req
* @param {[object]} res
* @return {[type]}
*/
exports.detailList = (req, res, next) => {
let params = req.query;
starModel.getDetailData(params).then((result) => {
res.render('star/list', _.assign({
layout: false,
params: params,
list: result,
isApp: req.query.app_version || req.query.appVersion || false
}));
}).catch(next);
};
... ...
/**
* sub app guang
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
var express = require('express'),
path = require('path'),
hbs = require('express-handlebars');
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.set('views', path.join(__dirname, 'views/action'));
app.engine('.hbs', hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
helpers: global.yoho.helpers
}));
// router
app.use(require('./router'));
module.exports = app;
... ...
/**
* 新潮教室
* @author: wsl<shuiling.wang@yoho.cn>
* @date: 2016/05/30
*/
'use strict';
const utils = '../../../utils';
const contentCodeConfig = require('../../../config/content-code');
const resourcesProcess = require(`${utils}/resources-process`);
const sign = global.yoho.sign;
const logger = global.yoho.logger;
const camelCase = global.yoho.camelCase;
const helpers = global.yoho.helpers;
const _ = require('lodash');
const serviceAPI = global.yoho.ServiceAPI;
const api = global.yoho.API;
const contentCode = contentCodeConfig.guang;
/**
* 获取资源位数据
* @param {[string]} page
* @return {[array]}
*/
const _getResources = (page) => {
return serviceAPI.get('operations/api/v5/resource/get', sign.apiSign({
content_code: contentCode[page],
client_type: 'iphone'
})).then((result) => {
if (result && result.code === 200) {
return resourcesProcess(result.data);
} else {
logger.error('星潮教室页面资源位返回 code 不是 200');
return [];
}
});
};
/**
* 星潮教室首页数据处理
* @param {[array]} list
* @return {[array]}
*/
const _processIndexData = (list, uid) => {
const formatData = {
ads: [],
starAvatar: [],
articles: []
};
list = list || {};
list = camelCase(list);
// 首页资源位数据处理
if (list.ads) {
_.forEach(list.ads.data, (data) => {
formatData.ads.push({
src: data.src,
url: data.url
});
});
}
// 首页明星文章数据处理
if (list.list) {
_.forEach(list.list, (data) => {
const avatar = {
tags: []
};
if (data.ext.tags.length > 1) {
avatar.isSwiper = true;
}
_.forEach(data.ext.tags, (tags) => {
avatar.tags.push({
avatarUrl: `/guang/star/detail?tag=${tags.tagName}&uid=${uid}&openby:yohobuy{"action":"go.h5","params":{"id":"","share":"","shareparam":{},"islogin":"N","type":0,"updateflag":"N","url":"http://m.yohobuy.com/guang/star/detail","param":{"tag":"${tags.tagName}"}}}`, // eslint-disable-line
cover: tags.cover,
tagName: tags.tagName
});
});
formatData.articles.push(_.merge({
id: data.id,
url: data.url,
title: data.title,
articeTxt: data.intro,
src: data.src,
publishTime: helpers.dateFormat('MM月DD日 hh:mm', data.publishTime),
viewsNum: data.viewsNum
}, avatar));
});
}
// 首页明星头像数据处理
if (list.tags) {
_.forEach(list.tags, (data) => {
let url = `/guang/star/detail?tag=${data.tagName}&uid=${uid}&openby:yohobuy{"action":"go.h5","params":{"id":"","share":"","shareparam":{},"islogin":"N","type":0,"updateflag":"N","url":"http://m.yohobuy.com/guang/star/detail","param":{"tag":"${data.tagName}"}}}`; // eslint-disable-line
formatData.starAvatar.push({
url: url,
cover: data.cover
});
});
}
return formatData;
};
/**
* 明星专题列表数据处理
* @param {[array]} list
* @return {[array]}
*/
const _processDetailData = (list) => {
const formatData = [];
list = list || [];
list = camelCase(list);
_.forEach(list, (data, key) => {
data.publishTime = helpers.dateFormat('MM月DD日 hh:mm', data.publishTime);
if (key < 4) {
data.islazy = true;
}
formatData.push(data);
});
return formatData;
};
/**
* 星搭配文章列表数据处理
*/
const _processCollocationData = (list) => {
const formatData = [];
list = list || [];
list = camelCase(list);
_.forEach(list, (data, key) => {
if (data.isFavor === 'N') {
data.isCollected = false;
} else {
data.isCollected = true;
}
if (key < 4) {
data.islazy = true;
}
formatData.push(data);
});
return formatData;
};
/**
* 星潮首页
*/
const getIndexData = (uid) => {
return api.get('', sign.apiSign({
method: 'app.starClass.index',
code: '8adc27fcf5676f356602889afcfd2a8e',
client_type: 'iphone'
})).then((result) => {
if (result && result.code === 200) {
return _processIndexData(result.data, uid);
} else {
logger.error('星潮教室首页数据返回 code 不是 200');
return {};
}
});
};
/**
* 明星专题
*/
const getDetailData = (params) => {
return api.get('', sign.apiSign({
method: 'app.starClass.lastTagArticle',
tag: params.tag,
page: params.page || 1,
size: 10,
client_type: 'iphone'
})).then((result) => {
if (result && result.code === 200) {
if (params.page > result.data.totalPage) {
return '';
} else {
return _processDetailData(result.data.list);
}
} else {
logger.error('明星专题文章数据返回 code 不是 200');
return [];
}
});
};
/**
* 星专题
*/
const getSpecialData = () => {
return _getResources('special').then((result) => {
// 数据结构嵌套太深
_.forEach(result, (data, key) => {
_.map(data.data, (item) => {
if (!_.isObject(item)) {
return;
}
if (key < 4) {
item.islazy = true;
}
return item;
});
});
return result;
});
};
/**
* 星搭配
*/
const getCollocationListData = (params, uid) => {
return serviceAPI.get('guang/api/v5/article/getStarClassroomArticleList', sign.apiSign(Object.assign({
limit: '20',
uid: uid
}, params))).then((result) => {
if (result && result.code === 200) {
return _processCollocationData(result.data.list.artList);
} else {
logger.error('获取星搭配文章列表返回 code 不是 200');
return [];
}
});
};
const setFavorite = (params, uid) => {
if (!uid) {
return Promise.resolve({
code: 400,
message: '未登录'
});
}
return api.get('', sign.apiSign({
method: params.type === 'del' ? 'app.sns.cancelFavorBackCount' : 'app.sns.setFavorBackCount',
client_type: 'h5',
article_id: params.articleId,
uid: uid
}));
};
module.exports = {
getIndexData: getIndexData,
getSpecialData: getSpecialData,
getCollocationListData: getCollocationListData,
setFavorite: setFavorite,
getDetailData: getDetailData
};
... ...
/**
* router of sub app channel
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
const express = require('express');
const cRoot = './controllers';
const star = require(cRoot + '/star');
const router = express.Router(); // eslint-disable-line
router.get('/star', star.index); // 星潮教室首页
router.get('/star/getIndexHtml', star.getIndexHtml); // 星潮教室首页
router.get('/star/detail', star.detail); // 明星文章专区
router.get('/star/detailList', star.detailList); // 明星文章专区文章Ajax请求
router.get('/star/special', star.special); // 星潮教室星专题
router.get('/star/collocation', star.collocation); // 星潮教室星搭配
router.get('/star/collocation/list', star.collocationList); // 星潮教室星搭配文章请求
router.post('/star/setFavorite', star.setFavorite); // 收藏文章
module.exports = router;
... ...
<div class="star-page yoho-page">
{{> star/head-tab}}
<ul class="collocation-list"></ul>
</div>
\ No newline at end of file
... ...
<div class="star-page yoho-page">
<ul class="detail-list" data-name="{{title}}">
{{> star/list}}
</ul>
</div>
\ No newline at end of file
... ...
{{> star/index-html}}
... ...
<div class="star-page yoho-page">
{{> star/head-tab}}
<div class="swiper-num"></div>
<div class="star-main">
{{> star/index-html}}
</div>
</div>
... ...
{{> star/list}}
\ No newline at end of file
... ...
<div class="star-page yoho-page">
{{> star/head-tab}}
<ul class="special-list">
{{#each resources}}
{{# data}}
{{#if url}}
<li data-bp-id="guang_subjectList_{{title}}_false" class="buriedpoint">
<a href='{{url}}'>
{{#if islazy}}
<img src="{{image src 640 310}}" alt="{{alt}}"/>
{{else}}
<img class="lazy" data-original="{{image src 640 310}}" alt="{{alt}}"/>
{{/if}}
<p>{{title}}</p>
</a>
</li>
{{/if}}
{{/ data}}
{{/each}}
</ul>
</div>
... ...
<ul class="head-tab" {{#if notIndex}}style="position: fixed;"{{/if}}>
{{#each headTab}}
<li {{#if cur}} class="cur" {{/if}} ><a href="{{ url }}" data-bp-id="guang_tab_{{name}}_false">{{ name }}</a></li>
{{/each}}
</ul>
\ No newline at end of file
... ...
<div class="loading-tip">下拉刷新</div>
<div class="star-wrap">
<div class="star-content">
{{#content}}
{{#if focus}}
{{> resources/banner-top}}
{{/if}}
{{/content}}
<div class="avatar-wrap">
<div class="avatar-swiper avatar">
<ul class="clearfix swiper-wrapper">
{{# starAvatar}}
<li class="swiper-slide">
<a href='{{url}}' data-avatar="{{image cover 180 180}}" class="rank-avatar"></a>
</li>
{{/ starAvatar}}
</ul>
</div>
</div>
<ul class="star-info clearfix">
{{#each articles}}
<li data-id="{{id}}">
<div class="star-avatar">
{{#if isSwiper}}
<div class="article-avatar-swiper">
<ul class="clearfix swiper-wrapper">
{{#each tags}}
<li class="swiper-slide">
<a href='{{avatarUrl}}'>
<div data-avatar="{{image cover 100 100}}" class="rank-avatar" ></div>
<p class="name">{{tagName}}</p>
</a>
</li>
{{/each}}
</ul>
</div>
{{else}}
{{# tags}}
<a href="{{avatarUrl}}">
<div data-avatar="{{image cover 100 100}}" class="rank-avatar" ></div>
<p class="name">{{tagName}}</p>
</a>
{{/ tags}}
{{/if}}
</div>
<a class="star-article" href='{{url}}'>
<i class="article-arrow"></i>
<h2 class="article-title">{{title}}</h2>
<div class="artice-cont">
<p>{{articeTxt}}</p>
<div class="artice-imgs-area">
<img src="{{image src 266 266}}" />
{{!-- <ul class="artice-imgs">
{{#each articeImg}}
<li><img src="{{image . 640 640}}" /></li>
{{/each}}
</ul> --}}
</div>
</div>
<div class="artice-o">
<span class="time"><i class="iconfont time-ico">&#xe603;</i>{{publishTime}}</span>
<span class="see"><i class="iconfont see-ico">&#xe602;</i>{{viewsNum}}</span>
</div>
</a>
</li>
{{/each}}
</ul>
</div>
</div>
{{!-- <div class="view-img">
<div class="mask-bg"></div>
<div class="view-area">
<div class="swiper-view">
<ul class="clearfix swiper-wrapper"></ul>
</div>
</div>
</div> --}}
... ...
{{#each list}}
<li articleId="{{id}}" data-bp-id="guang_collocationList_{{title}}_false" class="buriedpoint">
{{#if src}}
<a href="{{url}}">
{{#if islazy}}
<img src ="{{image src 640 310}}"/>
{{else}}
<img class="lazy" src ="" data-original="{{image src 640 310}}" />
{{/if}}
</a>
{{/if}}
<div class="cont-area ">
<a href="{{url}}"><h2 class="title">{{title}}</h2></a>
<p class="cont-txt">{{intro}}</p>
<div class="count-area">
<span class="time"><i class="iconfont time-ico">&#xe603;</i>{{publishTime}}</span>
<span class="see"><i class="iconfont see-ico">&#xe602;</i>{{viewsNum}}</span>
{{#if share.url}}
<a href="{{share.url}}" class="iconfont forward">&#xe600;</a>
{{/if}}
<span class="collection"><i class="iconfont collected-ico {{#isCollected}} collected {{/isCollected}}">&#xe605;</i></span>
</div>
</div>
</li>
{{/each}}
... ...
/**
* 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 md5 = require('md5');
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.area || '86';
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 = helpers.verifyAreaMobile(username, area);
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.signin(area, username, password, shoppingKey).then((result) => {
if (result.code && result.code === 200 && result.data.uid) {
done(null, result.data);
} 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',
callbackURL: `${siteUrl}/passport/login/alipay/callback`
}));
... ...
/**
* 找回密码
* 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 SIGN_IN_URL = helpers.urlFormat('/signin.html');
/**
* 通过邮箱找回密码页面
*/
module.exports.indexByEmailPage = (req, res) => {
let data = {
backUrl: SIGN_IN_URL,
headerText: '找回密码',
isPassportPage: true,
backEmail: true
};
res.render('back/email', Object.assign({
module: 'passport',
page: 'back-email',
title: '找回密码-通过邮箱'
}, data));
};
/**
* 发送验证码到邮箱
*/
module.exports.sendCodeToEmailAPI = (req, res) => {
let email = req.body.email || '';
let error = {
code: 400,
message: '邮箱格式不正确,请重新输入',
data: ''
};
if (!helpers.verifyEmail(email)) {
res.json(error);
return;
}
service.sendCodeToEmailAsync(email).then(result => {
if (result.code === 200) {
result.data = helpers.urlFormat('/passport/back/success.html', {email: email});
}
res.json(result);
}).catch(() => {
res.json(error);
});
};
/**
* 重新发送验证码到邮箱
*/
module.exports.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
});
});
};
/**
* 邮箱找回密码-返回成功页面
*/
module.exports.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.html'),
headerText: '找回密码',
isPassportPage: true,
backEmailSuccess: true,
goEmail: emailUrl,
resendUrl: helpers.urlFormat('/passport/back/resendemail', {email: email})
}));
};
/**
* 根据邮箱修改密码
*/
module.exports.setNewPasswordByEmailAPI = (req, res) => {
let pwd = req.body.password || '';
let code = req.body.code || '';
let data = {
code: 200,
data: SIGN_IN_URL
};
service.modifyPasswordByEmailAsync(pwd, code).then(result => {
if (result.includes('history.back')) {
data.code = 400;
data.message = '修改失败';
}
res.json(data);
}).catch(() => {
res.json(data);
});
};
/**
* 找回密码页面-通过手机号
*/
module.exports.indexByMobilePage = (req, res, next) => {
service.getAreaDataAsync()
.then(result => {
res.render('back/mobile', Object.assign({
module: 'passport',
page: 'back-mobile',
title: '找回密码-通过手机号'
}, {
backUrl: SIGN_IN_URL,
headerText: '找回密码',
isPassportPage: true,
backMobile: true,
countrys: result.data,
areaCode: '+86'
}));
}).catch(next);
};
/**
* 发送手机验证码
*/
module.exports.sendCodeToMobileAPI = (req, res) => {
let result = {
code: 400,
message: '密码只能使用数字、字母和半角标点符号,请重新输入',
data: ''
};
let phoneNum = req.body.phoneNum || '';
let areaCode = req.body.areaCode || '86';
if (!helpers.verifyMobile(phoneNum)) {
res.json(result);
return;
}
service.sendCodeToMobileAsync(phoneNum, areaCode).then(data=> {
if (_.isEmpty(data)) {
return Promise.rejected('发送验证码出错');
}
if (data.code === 200) {
result.data = helpers.urlFormat('/passport/back/verifycode', {
phoneNum: phoneNum,
areaCode: areaCode
});
res.json(result);
} else {
return Promise.rejected('发送验证码出错');
}
}).catch(err => {
result.message = err;
res.json(result);
});
};
/**
* 校验验证码页面
*/
module.exports.verifyCodeByMobilePage = (req, res) => {
let phoneNum = req.query.phoneNum || '';
let areaCode = req.query.areaCode || '86';
res.render('back/mobile-code', Object.assign({
module: 'passport',
page: 'back-code',
title: '找回密码-通过手机号'
}, {
backUrl: helpers.urlFormat('/passport/back/mobile.html'),
headerText: '找回密码',
isPassportPage: true,
backCode: true,
areaCode: areaCode,
phoneNum: phoneNum
}));
};
/**
* 校验手机验证码
*/
module.exports.verifyCodeByMobileAPI = (req, res) => {
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 = helpers.urlFormat('/passport/back/backcode.html', {
phoneNum: phoneNum,
token: result.data.token,
areaCode: areaCode
});
}
res.json(result);
}).catch(() => res.json({code: 400, message: '验证码失败'}));
};
/**
* 找回密码页面,设置新密码页面-手机
*/
module.exports.setNewPasswordByMobilePage = (req, res) => {
let phoneNum = req.query.phoneNum || '';
let token = req.query.token || '';
let areaCode = req.query.areaCode || '86';
let code = req.query.code || '';
if (!token || (!helpers.verifyMobile(phoneNum) && !code)) {
res.redirect(400);
return;
}
res.render('back/new-password', Object.assign({
module: 'passport',
page: 'back-new-password',
title: '找回密码-输入新密码'
}, {
backUrl: SIGN_IN_URL,
headerText: '找回密码',
isPassportPage: true,
backNewPwd: true,
phoneNum: phoneNum,
token: token,
areaCode: areaCode,
code: code
}));
};
/**
* 根据手机验证码修改密码
*/
module.exports.setNewPasswordByMobileAPI = (req, res) => {
let phoneNum = req.body.phoneNum || '';
let token = req.body.token || '';
let areaCode = req.body.areaCode || '86';
let newPwd = req.body.password || '';
service.modifyPasswordByMobileAsync(phoneNum, token, newPwd, areaCode)
.then(result => {
if (result.code === 200) {
result.data = SIGN_IN_URL;
}
res.json(result);
}).catch(() => res.json({code: 400, message: '修改密码失败'}));
};
... ...
/**
* 手机号绑定功能
*
* @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 Sources = {
qq: 'QQ',
sina: '微博',
alipay: '支付宝',
wechat: '微信'
};
const bind = {
indexPage: (req, res) => {
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;
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: 'http://chat8.live800.com/live800/chatClient/chatbox.jsp?companyID=620092&configID=149091&jid=8732423409&info=', // 在线客服
module: 'passport',
page: 'bind',
title: '绑定手机号'
});
},
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: 'bind-code',
title: '验证手机'
});
},
bindCheck: (req, res) => {
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;
} else {
data.data = result.data;
}
res.json(data);
});
} else {
res.json({
code: 400,
message: '',
data: ''
});
}
},
sendBindMsg: (req, res) => {
let phoneNum = req.body.phoneNum;
let areaCode = req.body.areaCode;
if (req.xhr && _.isNumber(parseInt(phoneNum, 0)) && areaCode) {
BindService.sendBindMsg(areaCode, phoneNum).then(result => {
if (result && result.code) {
res.json(result);
} else {
res.json({ code: 400, message: '', data: '' });
}
});
} else {
res.json({ code: 400, message: '', data: '' });
}
},
checkBindMsg: (req, res) => {
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: '' });
}
});
} else {
res.json({ code: 400, message: '', data: '' });
}
},
bindMobile: (req, res) => {
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 = req.cookies.refer;
refer = refer ? decodeURI(refer) : helpers.urlFormat();
if (result && result.code && result.code === 200 && result.data.uid) {
AuthHelper.syncUserSession(result.data.uid, req, res);
result.data.refer = refer;
}
return result;
}).then(result => {
res.json(result);
});
} else {
res.json({ code: 400, message: '', data: '' });
}
},
relateMobile: (req, res) => {
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, code);
} 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) {
AuthHelper.syncUserSession(result.data.uid, req, res);
result.data.refer = refer;
}
return result;
}).then(result => {
res.json(result);
});
} else {
res.json({ code: 400, message: '', data: '' });
}
},
successPage: (req, res) => {
res.render('bind/success', {
isPassportPage: true,
successTip: '恭喜您,第三方账号关联手机号码成功!',
goUrl: helpers.urlFormat(),
module: 'passport',
page: 'bind-success',
title: '绑定手机号'
});
},
changeCheck: (req, res) => {
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);
});
} else {
res.json({ code: 400, message: '', data: '' });
}
},
changeMobile: (req, res) => {
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);
});
} else {
res.json({ code: 400, message: '', data: '' });
}
}
};
module.exports = bind;
... ...
/**
* 登录
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
const passport = require('passport');
const md5 = require('md5');
const uuid = require('uuid');
const cookie = global.yoho.cookie;
const helpers = global.yoho.helpers;
const log = global.yoho.logger;
const config = global.yoho.config;
const AuthHelper = require('../models/auth-helper');
const loginPage = `${config.siteUrl}/passport/login/index`;
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 (/sign|login/.test(refer)) {
refer = `${config.siteUrl}/home`;
}
if (openId && nickname) {
return AuthHelper.signinByOpenID(nickname, openId, sourceType, shoppingKey).then((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).then(() => {
return refer;
});
}
}).then((redirectTo) => {
return res.redirect(redirectTo);
});
}
}
const common = {
beforeLogin: (req, res, next) => {
let refer = req.query.refer;
if (!refer) {
refer = req.get('Referer');
}
refer && res.cookie('refer', encodeURI(refer), {
domain: 'yohobuy.com'
});
next();
}
};
const local = {
loginPage: (req, res) => {
// 设置登录有效时间30分钟, 防机器刷,cache不稳定,改为cookie
res.cookie('LE' + md5('_LOGIN_EXPIRE'), (new Date()).getTime() / 1000 + 1800);
// 清除cookie
res.clearCookie('_UID');
res.clearCookie('_TOKEN');
res.render('login', {
loginIndex: true, // 模板中使用JS的标识
backUrl: 'javascript:history.go(-1)', // 返回的URL链接
showHeaderImg: true, // 控制显示头部图片
isPassportPage: true, // 模板中模块标识
registerUrl: '/reg.html', // 注册的URL链接
aliLoginUrl: '/passport/login/alipay', // 支付宝快捷登录的URL链接
weiboLoginUrl: '/passport/login/sina', // 微博登录的URL链接
qqLoginUrl: '/passport/login/qq', // 腾讯QQ登录的URL链接
wechatLoginUrl: '/passport/login/wechat', // 微信登录的URL链接
internationalUrl: '/login.html', // 国际号登录的URL链接
phoneRetriveUrl: '/passport/back/mobile', // 通过手机号找回密码的URL链接
emailRetriveUrl: '/passport/back/email', // 通过邮箱找回密码的URL链接
module: 'passport',
page: 'login',
title: '登录'
});
},
login: (req, res, next) => {
passport.authenticate('local', (err, user) => {
if (err) {
res.json({
code: 400,
message: err,
data: ''
});
} 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`;
}
user.session = refer;
user.href = refer;
AuthHelper.syncUserSession(user.uid, req, res).then(() => {
res.json({
code: 200,
data: user
});
});
}
})(req, res, next);
},
logout: (req, res) => {
req.session = null;
res.clearCookie('_UID', {
domain: 'yohobuy.com'
});
res.clearCookie('_TOKEN', {
domain: 'yohobuy.com'
});
res.clearCookie('_SPK');
let refer = req.get('Referer') || config.siteUrl;
res.redirect(refer);
}
};
const wechat = {
login: (req, res, next) => {
req.session = req.session || {};
req.session.authState = uuid.v4();
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) {
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;
doPassportCallback(openId, nickname, 'wechat', req, res).catch(next);
})(req, res, next);
} else {
return next(new Error('Auth State Mismatch'));
}
}
};
const sina = {
login: (req, res, next) => {
req.session = req.session || {};
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) {
log.error(`sina authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
let nickname = user.screen_name;
let openId = user.id;
doPassportCallback(openId, nickname, 'sina', req, res).catch(next);
})(req, res, next);
} else {
return next(new Error('Auth State Mismatch'));
}
}
};
const qq = {
login: (req, res, next) => {
req.session = req.session || {};
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.openid;
doPassportCallback(openId, nickname, 'qq', req, res).catch(next);
})(req, res, next);
} else {
return next(new Error('Auth State Mismatch'));
}
}
};
const alipay = {
login: (req, res, next) => {
return passport.authenticate('alipay')(req, res, next);
},
callback: (req, res, next) => {
passport.authenticate('alipay', (err, user) => {
if (err) {
log.error(`alipay authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
let nickname = user.realName;
let openId = user.userId;
doPassportCallback(openId, nickname, 'alipay', req, res).catch(next);
})(req, res, 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 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');
const reg = {
index: (req, res) => {
// 设置注册有效时间30分钟, 防机器刷
req.session._REG_EXPIRE = Date.now() + 1800000;
let refer = req.query.refer;
refer && res.cookie('refer', encodeURI(refer), {
domain: 'yohobuy.com'
});
res.render('reg/index', {
title: '注册',
backUrl: 'javascript:history.go(-1)', // eslint-disable-line
headerText: '注册', // 头部信息
isPassportPage: true, // 模板中模块标识
areaCode: '+86', // 默认的区号
countrys: RegService.getAreaData() // 地区信息列表
});
},
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) {
return res.json(data);
}
/* 返回跳转到验证页面的链接 */
if (result.code === 200) {
let token = sign.makeToken(mobile);
result.data = helpers.urlFormat('/passport/reg/code', {
token: token,
phoneNum: mobile,
areaCode: area
});
}
return res.json(result);
}).catch(next);
},
code: (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'
});
}
res.render('reg/code', {
page: 'code',
title: '注册-验证码',
backUrl: '/?go=1', // eslint-disable-line
headerText: '注册', // 头部信息
isPassportPage: true, // 模板中模块标识
areaCode: area, // 默认的区号
phoneNum: mobile, // 手机号
token: token, // 访问令牌
serviceUrl: 'http://chat8.live800.com/live800/chatClient/chatbox.jsp?companyID=620092&configID=149091&jid=8732423409&info=' // 在线客服
});
},
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) => {
return result.code ? res.json(result) : res.json(data);
}).catch(next);
},
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);
}
/* 返回跳转到设置密码的链接 */
if (result.code === 200) {
let token = sign.makeToken(mobile);
result.data = helpers.urlFormat('/passport/reg/password', {
token: token,
phoneNum: mobile,
areaCode: area
});
} else if (result.code === 404) {
result.message = '验证码错误'; // 统一验证提示
}
return res.json(result);
}).catch(next);
},
password: (req, res, next) => {
let token = req.query.token;
let mobile = +req.query.phoneNum;
let area = +(req.query.areaCode || 86);
// 判断是否允许访问, 不允许则跳转到错误页面
if (!_.isString(token) || !_.isNumber(mobile) || !_.isNumber(area) || !sign.verifyToken(mobile, token)) {
return next({
code: 403,
message: 'error token or mobile'
});
}
res.render('reg/password', {
page: 'password',
title: '注册-设置密码',
backUrl: '/?go=1', // eslint-disable-line
headerText: '注册', // 头部信息
isPassportPage: true, // 模板中模块标识
areaCode: area, // 默认的区号
phoneNum: mobile, // 手机号
token: token // 访问令牌
});
},
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;
/* 判断参数是否合法 */
if (!_.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 (!helpers.verifyPassword(password)) {
return res.json(data);
}
// 购物车key
let shoppingKey = cookie.getShoppingKey(req);
// 验证注册的标识码是否有效
RegService.regMobile(area, mobile, password, shoppingKey).then((result) => {
if (!result.code || result.code !== 200) {
return Promise.reject(result);
}
if (!result.data || !result.data.uid) {
return Promise.reject(result);
}
return AuthHelper.syncUserSession(result.data.uid, req, res);
}).then(() => {
// 返回跳转到来源页面
let refer = req.cookies.refer;
if (refer) {
refer = decodeURI(req.cookies.refer);
} else {
refer = '/home';
}
if (/sign|login/.test(refer)) {
refer = '/home';
}
return res.json({
code: 200,
message: '注册成功',
data: {
session: refer,
href: refer
}
});
}).catch(next);
}
};
module.exports = reg;
... ...
/**
* sub app channel
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
var express = require('express'),
path = require('path'),
hbs = require('express-handlebars');
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.set('views', path.join(__dirname, 'views/action'));
app.engine('.hbs', hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
helpers: global.yoho.helpers
}));
require('./auth');
app.use(passport.initialize());
app.use(passport.session());
// router
app.use(require('./router'));
module.exports = app;
... ...
'use strict';
const sign = global.yoho.sign;
const api = global.yoho.API;
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('', sign.apiSign(param));
}
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('', sign.apiSign(param));
}
static profile(uid) {
let param = {
uid: uid,
method: 'app.passport.profile'
};
return api.get('', sign.apiSign(param));
}
static syncUserSession(uid, req, res) {
return Auth.profile(uid).then((userInfo) => {
let token = sign.makeToken(uid);
let data = userInfo.data;
if (data) {
let uidCookie = `${data.profile_name}::${data.uid}::${data.vip_info.title}::${token}`;
res.cookie('_UID', uidCookie, {
domain: 'yohobuy.com'
});
}
req.session._TOKEN = token; // esline-disable-line
req.session._LOGIN_UID = uid; // esline-disable-line
res.cookie('_TOKEN', token, {
domain: 'yohobuy.com'
}); // esline-disable-line
});
}
}
module.exports = Auth;
... ...
/**
* Created by Tao.Huang on 2016/6/14.
*/
'use strict';
const sign = global.yoho.sign;
const api = global.yoho.API;
const YOHOBUY_URL = 'http://www.yohobuy.com/';
/**
* 获取地区数据
*/
module.exports.getAreaDataAsync = () => {
return api.get('', sign.apiSign({
method: 'app.passport.getArea'
})).then(result => {
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;
});
};
/**
* 通过邮箱找回密码
*
* @param string mail 邮箱地址
*/
module.exports.sendCodeToEmailAsync = (email) => {
return api.get('', sign.apiSign({
method: 'app.register.backpwdByEmail',
email: email
}));
};
/**
* 根据邮箱验证码修改密码(调用www.yohobuy.com接口)
*
* @param string pwd 新密码
* @param string code 邮箱验证码
*/
module.exports.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);
};
/**
* 通过手机找回密码
*
* @param string mobile 手机号
* @param integer area 地区码ID
*/
module.exports.sendCodeToMobileAsync = (mobile, area) => {
area = area || 86;
return api.get('', sign.apiSign({
mobile: mobile,
area: area,
method: 'app.register.sendBackpwdCodeToMobile'
}));
};
/**
* 校验密码修改手机验证码
*
* @param string mobile 手机号
* @param string code 验证码
* @param integer area 地区码ID
*/
module.exports.validateMobileCodeAsync = (mobile, code, area) => {
area = area || 86;
return api.get('', sign.apiSign({
mobile: mobile,
code: code,
area: area,
method: 'app.register.validBackpwdCode'
}));
};
/**
* 根据手机验证码修改密码
*
* @param string mobile 手机号
* @param string token 验证手机验证码返回的token
* @param integer area 地区码ID
*/
module.exports.modifyPasswordByMobileAsync = (mobile, token, newpwd, area)=> {
area = area || 86;
return api.get('', sign.apiSign({
mobile: mobile,
token: token,
newpwd: newpwd,
area: area,
method: 'app.register.changepwdByMobileCode'
}));
};
... ...
/**
* 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;
class RegService {
static 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);
}
static sendBindMsg(area, mobile) {
let params = {
method: 'app.passport.smsbind',
mobile: mobile,
area: area
};
return api.get('', params);
}
static checkBindCode(area, mobile, code) {
return api.get('', {
method: 'app.register.validRegCode',
mobile: mobile,
area: area,
code: code
});
}
static 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);
}
static relateMobile(openId, sourceType, mobile, area) {
return api.get('', {
method: 'app.passport.relateMobile',
mobile: mobile,
openId: openId,
source_type: sourceType,
area: area
});
}
static changeCheck(mobile, area) {
return api.get('', {
method: 'app.passport.changeCheck',
mobile: mobile,
area: area
});
}
static changeMobile(uid, mobile, area, code) {
return api.get('', {
method: 'app.passport.changeMobile',
mobile: mobile,
uid: uid,
code: code,
area: area
});
}
}
module.exports = RegService;
... ...
/**
* passport.js 支付宝登录插件
*
* @author JiangFeng<jeff.jiang@yoho.cn>
* @date 2016/06/21
*/
'use strict';
const util = require('util');
const _ = require('lodash');
const md5 = require('md5');
const Strategy = require('passport-strategy');
// 支付宝网关地址
const ALIPAY_URL = 'https://mapi.alipay.com/gateway.do';
const defaultOptions = {
service: 'alipay.auth.authorize',
_input_charset: 'utf-8', // esline-disable-line
sign_type: 'MD5',
target_service: 'user.auth.quick.login'
};
/**
* 将参数排序,拼接成 "参数=参数值" 的格式
*
* @param {Object} params
*/
function paramsToRaw(params) {
let keys = Object.keys(params);
keys = keys.sort();
let newArgs = {};
keys.forEach((key) => {
newArgs[key] = params[key];
});
let string = '';
for (let k of newArgs) {
string += '&' + k + '=' + newArgs[k];
}
string = string.substr(1);
return string;
}
function AlipayStrategy(options) {
Strategy.call(this);
this.name = 'alipay';
this._passReqToCallback = options.passReqToCallback;
}
util.inherits(AlipayStrategy, Strategy);
AlipayStrategy.prototype.authenticate = (req, options) => {
if (req.query && req.query.is_success && req.query.sign && req.query.sign_type) {
// sign check
let query = req.query;
let sign = query.sign;
let signType = query.sign_type;
delete query.sign_type;
delete query.sign;
let signString = paramsToRaw(query) + options.key;
if (signType === 'MD5' && sign !== md5(signString)) {
this.error('alipay callback sign check fail');
this.fail('alipay callback sign check fail');
return;
}
if (req.query.is_success === 'T') {
let user = {
userId: req.query.user_id,
realName: req.query.realName,
email: req.query.email
};
this.success(user, null);
} else {
this.error('alipay login fail');
this.fail(req.error_code);
}
} else {
let params = _.extends(defaultOptions, options);
let signType = params.sign_type;
delete params.sign_type;
delete params.sign;
let signString = paramsToRaw(params) + options.key;
if (signType === 'MD5') {
params.sign = md5(signString);
params.sign_type = 'MD5';
}
this.redirect(ALIPAY_URL + '?' + paramsToRaw(params));
}
};
module.exports.Strategy = AlipayStrategy;
... ...
/**
* 注册数据接口
*
* @author JiangFeng<jeff.jiang@yoho.cn>
* @date 2016/06/21
*/
'use strict';
const api = global.yoho.API;
class RegService {
static 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: '中国香港'
}];
}
static sendCodeToMobile(area, mobile) {
let params = {
method: 'app.register.sendRegCodeToMobile',
area: area,
mobile: mobile
};
return api.post('', params);
}
static validMobileCode(area, mobile, code) {
let params = {
method: 'app.register.validRegCode',
area: area,
mobile: mobile,
code: code
};
return api.post('', params);
}
static regMobile(area, mobile, password, shoppingKey) {
let params = {
method: 'app.passport.register',
area: area,
profile: mobile,
password: password
};
if (shoppingKey) {
params.shopping_key = shoppingKey; // esline-disable-line
}
return api.post('', params);
}
}
module.exports = RegService;
... ...
/**
* router of sub app channel
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
const express = require('express');
const cRoot = './controllers';
const login = require(cRoot + '/login');
const back = require(cRoot + '/back');
const bind = require(cRoot + '/bind');
const reg = require(cRoot + '/reg');
const router = express.Router(); // eslint-disable-line
// 登出
router.get('/signout/index', login.local.logout);
// 登录页面
router.get('/login', login.common.beforeLogin, login.local.loginPage);
// 本地登录
router.post('/login/auth', login.local.login);
// 微信登录
router.get('/login/wechat', login.common.beforeLogin, login.wechat.login);
router.get('/login/wechat/callback', login.wechat.callback);
// sina登录
router.get('/login/sina', login.common.beforeLogin, login.sina.login);
router.get('/login/sina/callback', login.sina.callback);
// qq登录
router.get('/login/qq', login.common.beforeLogin, login.qq.login);
router.get('/login/qq/callback', login.qq.callback);
// 登录绑定
router.get('/bind/index', bind.indexPage);
router.post('/bind/bindCheck', bind.bindCheck);
router.get('/bind/code', bind.codePage);
router.post('/bind/sendBindMsg', bind.sendBindMsg);
router.post('/bind/bindMobile', bind.bindMobile);
router.post('/bind/relateMobile', bind.relateMobile);
router.get('/bind/success', bind.successPage);
router.post('/bind/changeCheck', bind.changeCheck);
router.post('/bind/changeMobile', bind.changeMobile);
/**
* 注册
*/
router.get('/reg/index', reg.index);
router.post('/reg/verifymobile', reg.verifyMobile);
router.get('/reg/code', reg.code);
router.post('/reg/sendcode', reg.sendCode);
router.post('/reg/verifycode', reg.verifyCode);
router.get('/reg/password', reg.password);
router.post('/reg/setpassword', reg.setPassword);
/**
* 邮箱
*/
// 通过邮箱找回密码
router.get('/back/email.html', back.indexByEmailPage);
// 邮箱找回密码-成功
router.get('/back/success.html', back.backSuccessByEmailPage);
// 发送邮箱验证码
router.post('/back/sendemail', back.sendCodeToEmailAPI);
// 重新发送邮箱验证码
router.get('/back/resendemail', back.resendCodeToEmailAPI);
// 据邮箱修改密码
router.post('/back/passwordbyemail', back.setNewPasswordByEmailAPI);
/**
* 手机
*/
// 通过手机找回密码
router.get('/back/mobile.html', back.indexByMobilePage);
// 发送手机验证码
router.get('/back/mobilecode.html', back.verifyCodeByMobilePage);
// 输入新密码
router.get('/back/backcode.html', back.setNewPasswordByMobilePage);
// 发送手机验证码
router.post('/back/sendcode', back.sendCodeToMobileAPI);
// 校验手机验证码
router.post('/back/verifycode', back.verifyCodeByMobileAPI);
// 根据手机验证码修改密码
router.post('/back/passwordbymobile', back.setNewPasswordByMobileAPI);
module.exports = router;
... ...
<div class="back-email-success-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
<p class="tip">验证邮件已发送至你的邮箱</p>
<p class="sub-tip">请在24小时内通过邮件内的链接设置新密码</p>
<a class="go-email btn" href={{goEmail}}>去邮箱看看</a>
<a id="resend" class="resend" data-url={{resendUrl}}>重新发送邮件</a>
</div>
</div>
... ...
<div class="back-email-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
<div class="input-container row has-clear">
<input id="email" class="input email" type="text" placeholder="请输入邮箱" autocomplete="off">
</div>
<span id="btn-sure" class="btn btn-sure disable row">确定</span>
</div>
</div>
... ...
<div class="back-code-page passport-page yoho-page">
{{> passport/code}}
</div>
... ...
<div class="back-mobile-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
{{> passport/country_list}}
<div class="input-container phone-container row has-clear">
<span id="area-code" class="area-code">{{areaCode}}</span>
<input id="phone-num" class="input phone-num" type="text" placeholder="手机号">
</div>
<span id="btn-next" class="btn btn-next disable row">下一步</span>
</div>
</div>
... ...
<div class="back-new-password-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
<div class="input-container row has-eye">
<input id="pwd" class="input pwd" type="text" placeholder="请输入新密码" autocomplete="off" maxlength="20">
</div>
<span id="btn-ok" class="btn btn-ok disable">完成</span>
</div>
{{#if phoneNum}}
<input id="phone-num" type="hidden" value={{phoneNum}}>
<input id="area-code" type="hidden" value={{areaCode}}>
<input id="token" type="hidden" value={{token}}>
{{/if}}
{{# code}}
<input id="email-code" type="hidden" value={{.}}>
{{/ code}}
</div>
... ...
<div class="reg-code-page passport-page yoho-page">
<input type="hidden" id="isReg" value="{{isReg}}">
<input type="hidden" id="openId" value="{{openId}}">
<input type="hidden" id="sourceType" value="{{sourceType}}">
<input type="hidden" id="nickname" value="{{nickname}}">
{{> passport/code}}
</div>
\ No newline at end of file
... ...
<div class="bind-page passport-page yoho-page">
<input type="hidden" id="openId" value="{{openId}}">
<input type="hidden" id="sourceType" value="{{sourceType}}">
<input type="hidden" id="nickname" value="{{nickname}}">
{{> passport/header}}
<div class="content">
{{#if isWechatLogin}}
<p class="bind-tip">您正在使用{{platform}}快捷登录,请绑定手机号</p>
{{^}}
<p class="bind-tip">正在使用{{platform}}登录</p>
{{/if}}
{{> passport/country_list}}
<div class="input-container phone-container row has-clear">
<span id="area-code" class="area-code">{{areaCode}}</span>
<input id="phone-num" class="input phone-num" type="text" placeholder="手机号">
</div>
<span id="btn-next" class="btn btn-next disable row">发送验证码</span>
{{#if isWechatLogin}}
<div class="other-login">
<a href="/passport/login/index">
用其他方式登录
</a>
</div>
{{/if}}
<div class="tip">
<p>
登录注册中遇到问题?请联系客服
</p>
<a href="{{serviceUrl}}">
<span class="iconfont icon">&#xe63c;</span>
在线客服
</a>
</div>
</div>
</div>
\ No newline at end of file
... ...
<div class="bind-password-page passport-page yoho-page">
<input type="hidden" id="openId" value="{{openId}}">
<input type="hidden" id="sourceType" value="{{sourceType}}">
<input type="hidden" id="nickname" value="{{nickname}}">
{{> passport/header}}
<div class="content">
{{#if bindPwd}}
<div class="bind-tip">
<p class="title">绑定成功!</p>
您以后还可以使手机号+密码的形式登录有货哦!
</div>
{{/if}}
<div class="input-container row has-eye">
<input id="pwd" class="input pwd" type="text" placeholder="请输入密码" autocomplete="off" maxlength="20">
</div>
<span id="btn-sure" class="btn btn-sure disable row">确定</span>
</div>
<input id="phone-num" type="hidden" value={{phoneNum}}>
<input id="area-code" type="hidden" value={{areaCode}}>
<input id="token" type="hidden" value={{token}}>
<input id="code" type="hidden" value={{code}}>
</div>
\ No newline at end of file
... ...
<div class="success-page passport-page yoho-page">
<div class="success-icon">
</div>
<div class="success-tip">
{{{successTip}}}
</div>
<a class="go" href="{{goUrl}}">
立即购物
</a>
</div>
\ No newline at end of file
... ...
<div class="login-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
<div class="acc-container input-container row has-clear">
<div class="yoho-logo"></div>
<input id="account" class="input account" type="text" placeholder="手机号/邮箱" autocomplete="off" value={{account}}>
</div>
<div class="input-container row has-eye">
<input id="pwd" class="pwd input" type="password" placeholder="密码">
</div>
<span id="btn-login" class="btn btn-login disable">登录</span>
<p class="op-container">
<a class="go-register" href={{registerUrl}}>免费注册</a>
<span id="forget-pwd" class="forget-pwd">忘记密码</span>
</p>
<div class="third-party-login">
<div class="tp-link">
<a class="qq" href={{qqLoginUrl}}></a>
<a class="wechat" href={{wechatLoginUrl}}></a>
<a class="weibo" href={{weiboLoginUrl}}></a>
<a class="alipay" href={{aliLoginUrl}}></a>
</div>
</div>
<a class="international" href={{internationalUrl}}>International Customer</a>
<div class="login-tip">
<div class="info-icon"></div>
Yoho!Family账号可登录Yoho!Buy有货
</div>
<div id="retrive-pwd-mask" class="mask"></div>
<ul id="retrive-pwd-ways" class="retrive-pwd-ways">
<li>
<a href={{phoneRetriveUrl}}>通过手机找回密码</a>
</li>
<li>
<a href={{emailRetriveUrl}}>通过邮箱找回密码</a>
</li>
<li id="cancel-retrive">
取消
</li>
</ul>
</div>
</div>
\ No newline at end of file
... ...
<div class="reg-code-page passport-page yoho-page">
{{> passport/code}}
</div>
... ...
<div class="reg-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
{{> passport/country_list}}
<div class="input-container phone-container row has-clear">
<span id="area-code" class="area-code">{{areaCode}}</span>
<input id="phone-num" class="input phone-num" type="text" placeholder="手机号">
</div>
<span id="btn-next" class="btn btn-next disable row">下一步</span>
<p class="register-tip">YOHO!Family账号可登录Yoho!Buy有货、YOHO!Boys、YOHO!Girls及SHOW</p>
</div>
</div>
... ...
<div class="reg-password-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
<div class="input-container row has-eye">
<input id="pwd" class="input pwd" type="text" placeholder="请输入密码" autocomplete="off" maxlength="20">
</div>
<span id="btn-sure" class="btn btn-sure disable row">确定</span>
</div>
<input id="phone-num" type="hidden" value="{{phoneNum}}">
<input id="area-code" type="hidden" value="{{areaCode}}">
<input id="token" type="hidden" value="{{token}}">
</div>
... ...
{{> passport/header}}
<div class="content">
<div class="text-container">
验证码已发送至
<span class="phone">
+{{areaCode}} {{phoneNum}}
</span>
</div>
<div class="input-container row has-clear">
<input id="captcha" class="input captcha" type="text" placeholder="验证码" maxlength="6" autocomplete="off">
<div id="captcha-tip" class="captcha-tip disable">重新发送 (60秒)</div>
</div>
<span id="btn-next" class="btn btn-next disable row">确定</span>
<div class="tip">
{{#if relateCode}}
注:关联的手机号不能用来登录此帐号
{{/if}}
</div>
<input id="phone-num" type="hidden" value={{phoneNum}}>
<input id="area-code" type="hidden" value={{areaCode}}>
<input id="token" type="hidden" value={{token}}>
</div>
... ...
<div class="select-container row">
<span class="select-title">国家和地区</span>
<select id="country-select" class="country-select select in-android-uc">
{{# countrys}}
<option value={{areaCode}} {{#if selected}}selected{{/if}}>{{name}}</option>
{{/ countrys}}
</select>
<div class="arrow-right"></div>
</div>
... ...
<div class="header">
<a class="go-back" href="{{#if backUrl}}{{backUrl}}{{^}}javascript:history.go(-1);{{/if}}"></a>
{{#showHeaderImg}}
<div class="img-header"></div>
{{/showHeaderImg}}
{{#headerText}}
<p class="title">{{.}}</p>
{{/headerText}}
</div>
\ No newline at end of file
... ...
... ... @@ -7,6 +7,7 @@
'use strict';
const mRoot = '../models';
const headerModel = require('../../../doraemon/models/header'); // 头部model
const detail = require(`${mRoot}/detail`); // 商品详情 model
const intro = require(`${mRoot}/intro`); // 商品尺码信息 model
const preference = require(`${mRoot}/preference`); // 商品偏好 model
... ... @@ -18,9 +19,13 @@ const _ = require('lodash');
* @param {[type]} res [description]
* @return {[type]} [description]
*/
exports.index = (req, res) => {
var vipLevel = 0; // 用户等级 待处理
var uid = _.isEmpty(req.user.uid) ? null : req.user.uid;
exports.index = (req, res, next) => {
let vipLevel = 0; // 用户等级 待处理
let uid = _.isEmpty(req.user.uid) ? null : req.user.uid;
let headerData = headerModel.setNav({
navTitle: '商品详情'
});
detail({
id: req.params[0],
... ... @@ -30,12 +35,13 @@ exports.index = (req, res) => {
ua: req.get('user-agent') || ''
}).then((result) => {
res.render('detail/detail', {
pageHeader: headerData,
result: result,
module: 'product',
page: 'detail',
title: result.goodsName
title: result.goodsName,
pageFooter: true
});
});
}).catch(next);
};
/**
... ...
/**
* 奥莱页面
* @author: 赵彪<bill.zhao@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
const outletModel = require('../models/outlet');
const headerModel = require('../../../doraemon/models/header');
const yhChannelEmpty = 0;
const willEndActivity = {
type: 2,
template: 'outlet/will-end',
page: 'outlet-will-end'
};
const willStartActivity = {
type: 3,
template: 'outlet/will-start',
page: 'outlet-will-start'
};
// 奥莱首页控制器
exports.index = (req, res, next) => {
let headerData = headerModel.setNav({
navTitle: 'OUTLET',
navBtn: false
});
let categoryId = req.query.category_id;
let yhChannel = req.query.yh_channel || yhChannelEmpty;
let contentcode = req.query.content_code;
outletModel.getContent(categoryId, yhChannel, contentcode).then(result => {
res.render('outlet', Object.assign({
pageHeader: headerData
}, result));
}).catch(next);
};
// 奥莱活动详情页
exports.activityDetail = (req, res, next) => {
outletModel.getActivity(req.query.id).then(result => {
let headerData = headerModel.setNav({
navTitle: result.activityTitle,
navBtn: false
});
res.render('outlet/activity', Object.assign({
page: 'outlet-detail',
pageHeader: headerData,
pageFooter: true
}, result));
}).catch(next);
};
// 奥莱活动频道列表页
exports.activityList = (req, res, next) => {
let headerData = headerModel.setNav({
navTitle: 'OUTLET',
navBtn: false
});
let categoryId = req.query.category_id;
let type = req.query.type || willEndActivity.type;
let template;
let page;
if (!categoryId) {
throw new Error('No parent_id for OUTLET channel page!');
}
// 根据请求的类型确定要渲染的是即将结束页面还是上线预告页面
if (parseInt(type, 10) === willEndActivity.type) {
template = willEndActivity.template;
page = willEndActivity.page;
} else {
template = willStartActivity.template;
page = willStartActivity.page;
}
outletModel.getRecentActivity(type, categoryId).then(result => {
res.render(template, Object.assign({
page: page,
pageHeader: headerData,
pageFooter: true
}, result));
}).catch(next);
};
... ...
... ... @@ -5,26 +5,46 @@
*/
'use strict';
const library = '../../../library';
const mRoot = '../models';
// const cookie = require(`${library}/cookie`);
const headerModel = require('../../../doraemon/models/header');
const log = require(`${library}/logger`);
const saleModel = require(`${mRoot}/sale`);
const queryParam = {
brand: '0',
gender: '1,2,3',
sort: '0',
size: '0',
price: '0',
pD: '0.1,0.9'
};
// const queryParam = {
// brand: '0',
// gender: '1,2,3',
// sort: '0',
// size: '0',
// price: '0',
// pD: '0.1,0.9'
// };
const saleLogger = (err, res) => {
log.error('sale页面渲染错误:' + JSON.stringify(err));
res.send('error');
/**
* 公共数据处理
* @param {[object]} req
* @param {[string]} title 标题
* @param {[string]} page js文件page名称
* @return {[type]}
*/
const processPublicData = (req, title, page) => {
let data = {};
let headerData = headerModel.setNav({
navTitle: title
});
data = {
channel: req.yoho.channel,
renderData: {
module: 'product',
page: page,
title: title,
pageHeader: headerData,
pageFooter: true
}
};
return data;
};
/**
... ... @@ -33,18 +53,11 @@ const saleLogger = (err, res) => {
* @param {[object]} res
* @return {[type]}
*/
exports.index = (req, res) => {
let headerData = headerModel.setNavHeader('SALE');
let renderData = {
module: 'product',
page: 'sale',
title: 'SALE',
pageHeader: headerData,
pageFooter: true
};
exports.index = (req, res, next) => {
let params = processPublicData(req, 'SALE', 'sale');
saleModel.getSaleData().then((result) => {
res.render('sale/index', Object.assign(renderData, queryParam, {
saleModel.getSaleData(params.channel).then((result) => {
res.render('sale/index', Object.assign(params.renderData, {
content: result,
floorHeader: {
title: {
... ... @@ -52,9 +65,7 @@ exports.index = (req, res) => {
}
}
}));
}).catch((err) => {
saleLogger(err, res);
});
}).catch(next);
};
/**
... ... @@ -63,23 +74,14 @@ exports.index = (req, res) => {
* @param {[object]} res
* @return {[type]}
*/
exports.breakingYards = (req, res) => {
let headerData = headerModel.setNavHeader('断码区');
let renderData = {
module: 'product',
page: 'break-code',
title: '断码区',
pageHeader: headerData,
pageFooter: true
};
exports.breakingYards = (req, res, next) => {
let params = processPublicData(req, '断码区', 'break-code');
saleModel.getBreakCodeData({
yhChannel: req.query.channel
yhChannel: params.channel
}).then((result) => {
res.render('sale/break-code', Object.assign(renderData, result));
}).catch((err) => {
saleLogger(err, res);
});
res.render('sale/break-code', Object.assign(params.renderData, result));
}).catch(next);
};
/**
... ... @@ -88,20 +90,12 @@ exports.breakingYards = (req, res) => {
* @param {[object]} res
* @return {[type]}
*/
exports.discount = (req, res) => {
let headerData = headerModel.setNavHeader('折扣专场');
let renderData = {
module: 'product',
page: 'discount',
pageHeader: headerData,
pageFooter: true
};
exports.discount = (req, res, next) => {
let params = processPublicData(req, '折扣专场', 'discount');
saleModel.getDiscountData().then((result) => {
res.render('sale/discount', Object.assign(renderData, result));
}).catch((err) => {
saleLogger(err, res);
});
saleModel.getDiscountData(params.channel).then((result) => {
res.render('sale/discount', Object.assign(params.renderData, result));
}).catch(next);
};
... ... @@ -111,22 +105,14 @@ exports.discount = (req, res) => {
* @param {[object]} res
* @return {[type]}
*/
exports.discountDetail = (req, res) => {
let headerData = headerModel.setNavHeader();
exports.discountDetail = (req, res, next) => {
let id = req.query.id;
let renderData = {
module: 'product',
page: 'discount-detail',
pageFooter: true
};
let params = processPublicData(req, '', 'discount-detail');
saleModel.getDiscountDetailData(id).then((result) => {
headerData.navTitle = result.title;
renderData.pageHeader = headerData;
res.render('sale/discount-detail', Object.assign(renderData, result));
}).catch((err) => {
saleLogger(err, res);
});
saleModel.getDiscountDetailData(id, params.channel).then((result) => {
params.renderData.pageHeader.navTitle = result.title;
res.render('sale/discount-detail', Object.assign(params.renderData, result));
}).catch(next);
};
/**
... ... @@ -135,23 +121,14 @@ exports.discountDetail = (req, res) => {
* @param {[object]} res
* @return {[type]}
*/
exports.vip = (req, res) => {
let headerData = headerModel.setNavHeader('会员专享');
let renderData = {
module: 'product',
page: 'vip',
title: '会员专享',
pageHeader: headerData,
pageFooter: true
};
exports.vip = (req, res, next) => {
let params = processPublicData(req, '会员专享', 'vip');
saleModel.getVipData().then((result) => {
res.render('sale/vip', Object.assign(renderData, {
saleModel.getVipData(params.channel).then((result) => {
res.render('sale/vip', Object.assign(params.renderData, {
content: result
}));
}).catch((err) => {
saleLogger(err, res);
});
}).catch(next);
};
/**
... ... @@ -160,7 +137,7 @@ exports.vip = (req, res) => {
* @param {[object]} res
* @return {[type]}
*/
exports.filter = (req, res) => {
exports.filter = (req, res, next) => {
let params = Object.assign({}, req.query);
saleModel.getFilterData(params).then((result) => {
... ... @@ -169,7 +146,7 @@ exports.filter = (req, res) => {
params: params,
filter: result
});
});
}).catch(next);
};
... ... @@ -179,7 +156,7 @@ exports.filter = (req, res) => {
* @param {[object]} res
* @return {[type]}
*/
exports.search = (req, res) => {
exports.search = (req, res, next) => {
let params = Object.assign({}, req.query);
// uid = 9239279
... ... @@ -190,14 +167,14 @@ exports.search = (req, res) => {
let vipObj = {};
if (req.query.saleType === '2') {
vipObj = Object.assign({
vipObj = {
saleVip: (req.query.saleType === '2' && (!uid || vipLevel === '0')),
vipLevel: vipLevel,
saleViplogin: vipLevel >= 1 ? true : false,
vipPrice1: vipLevel === '1',
vipPrice2: vipLevel === '2',
vipPrice3: vipLevel === '3'
}, vipObj);
};
}
res.render('sale/product', Object.assign({
... ... @@ -205,5 +182,5 @@ exports.search = (req, res) => {
params: params,
goods: result[0]
}, vipObj));
});
}).catch(next);
};
... ...
... ... @@ -4,30 +4,30 @@
* @date: 2016/05/06
*/
var express = require('express'),
path = require('path'),
hbs = require('express-handlebars');
var express = require('express'),
path = require('path'),
hbs = require('express-handlebars');
var app = express();
var app = express();
// set view engin
var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root
// 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.on('mount', function(parent) {
delete parent.locals.settings; // 不继承父 App 的设置
Object.assign(app.locals, parent.locals);
});
app.set('views', path.join(__dirname, 'views/action'));
app.engine('.hbs', hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
helpers: global.yoho.helpers
}));
app.set('views', path.join(__dirname, 'views/action'));
app.engine('.hbs', hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
helpers: global.yoho.helpers
}));
// router
app.use(require('./router'));
// router
app.use(require('./router'));
module.exports = app;
module.exports = app;
... ...
... ... @@ -6,15 +6,9 @@
'use strict';
const library = '../../../library';
const API = require(`${library}/api`).API;
const sign = require(`${library}/sign`);
const api = global.yoho.API;
const _ = require('lodash');
const helpers = require(`${library}/helpers`);
// const log = require(`${library}/logger`);
var api = new API();
const helpers = global.yoho.helpers;
/**
* 处理品牌关联店铺信息
... ... @@ -22,12 +16,13 @@ var api = new API();
* @return {array}
*/
const getShopsInfo = (data) => {
var enterStore = {};
let enterStore = {};
_.forEach(data, function(value, key) {
enterStore[key] = {};
enterStore[key].img = value.brand_ico;
enterStore[key].storeName = value.brand_name;
enterStore[key] = {
img: value.brand_ico,
storeName: value.brand_name
};
if (value.shop_id !== null && typeof value.shop_id !== 'undefined') {
let params = {};
... ... @@ -100,7 +95,7 @@ const procShowStatus = (data, showStatus, isBeginSale) => {
* @return {string} 限购商品跳转url
*/
const getLimitCodeUrl = (productCode, skn, ua) => {
var url = 'yohoapp://yoho.app/openwith?limit_product_code=' + productCode +
let url = 'yohoapp://yoho.app/openwith?limit_product_code=' + productCode +
'&product_skn=' + skn;
let isIphone = String(ua).indexOf('iPhone') >= 0;
... ... @@ -121,7 +116,7 @@ const getLimitCodeUrl = (productCode, skn, ua) => {
* @return dest Object 格式化数据
*/
const detailDataPkg = (origin, uid, vipLevel, ua) => {
var dest = {}, // 结果输出
let dest = {}, // 结果输出
colorGroup = {},
thumbImageList = {},
sizeGroup = {},
... ... @@ -192,14 +187,15 @@ const detailDataPkg = (origin, uid, vipLevel, ua) => {
// VIP 商品价格
dest.vipLevel = {};
dest.vipLevel.list = {};
dest.vipLevel.list = [];
if (origin.productPriceBo.vipPrices !== null && typeof origin.productPriceBo.vipPrices !== 'undefined') {
_.forEach(origin.productPriceBo.vipPrices, function(value, key) {
dest.vipLevel.list[key] = {};
dest.vipLevel.list[key].level = value.vipLevel;
dest.vipLevel.list[key].text = value.vipPrice;
dest.vipLevel.list[key].currentLevel = (value.vipLevel === vipLevel) ? true : false;
_.forEach(origin.productPriceBo.vipPrices, function(value) {
dest.vipLevel.list.push({
level: value.vipLevel,
text: value.vipPrice,
currentLevel: (value.vipLevel === vipLevel)
});
});
}
... ... @@ -224,16 +220,17 @@ const detailDataPkg = (origin, uid, vipLevel, ua) => {
// 商品咨询
dest.feedbacks = {};
dest.feedbacks.consults = {};
dest.feedbacks.consults = [];
dest.feedbacks.consultsNum = 0;
if (origin.consultBoWrapper !== null && typeof origin.consultBoWrapper !== 'undefined') {
dest.feedbacks.consultsNum = origin.consultBoWrapper.consultTotal;
_.forEach(origin.consultBoWrapper.consultBoList, function(value, key) {
dest.feedbacks.consults[key] = {};
dest.feedbacks.consults[key].question = value.ask;
dest.feedbacks.consults[key].time = value.askTime;
dest.feedbacks.consults[key].answer = value.answer;
_.forEach(origin.consultBoWrapper.consultBoList, function(value) {
dest.feedbacks.consults.push({
question: value.ask,
time: value.askTime,
answer: value.answer
});
});
let params = {};
... ... @@ -256,16 +253,15 @@ const detailDataPkg = (origin, uid, vipLevel, ua) => {
dest.feedbacks.commentsNum = 0;
if (origin.commentBoWrapper !== null && typeof origin.commentBoWrapper !== 'undefined') {
dest.feedbacks.commentsNum = origin.commentBoWrapper.commentTotal;
dest.feedbacks.comments = {};
_.forEach(origin.commentBoWrapper.commentBoList, function(value, key) {
dest.feedbacks.comments[key] = {};
dest.feedbacks.comments[key].userName = value.nickName;
dest.feedbacks.comments[key].desc = value.colorName +
'/' + value.sizeName;
dest.feedbacks.comments[key].content = (value.content !== null &&
typeof value.content !== 'undefined') ? value.content : '';
dest.feedbacks.comments[key].time = value.createTime;
dest.feedbacks.comments = [];
_.forEach(origin.commentBoWrapper.commentBoList, function(value) {
dest.feedbacks.comments.push({
userName: value.nickName,
desc: value.colorName + '/' + value.sizeName,
content: value.content ? value.content : '',
time: value.createTime
});
});
let params = {};
... ... @@ -500,23 +496,23 @@ const detailDataPkg = (origin, uid, vipLevel, ua) => {
};
module.exports = (data) => {
var finalResult;
var params = {};
params.productId = _.toString(data.id);
params.method = 'h5.product.data';
let finalResult;
let params = {
productId: _.toString(data.id),
method: 'h5.product.data'
};
if (!_.isEmpty(data.uid)) {
params.uid = data.uid;
}
return api.get('', sign.apiSign(params)).then(result => {
return api.get('', params).then(result => {
finalResult = detailDataPkg(result, data.uid, data.vipLevel, data.ua);
return api.get('', sign.apiSign({
return api.get('', {
method: 'app.shop.queryShopsByBrandId',
brand_id: _.toString(result.brandId)
})).then(shops => {
}).then(shops => {
if (shops.code === 200) {
finalResult.enterStore = getShopsInfo(shops.data);
}
... ...
... ... @@ -7,14 +7,11 @@
'use strict';
const library = '../../../library';
const API = require(`${library}/api`).API;
const sign = require(`${library}/sign`);
const _ = require('lodash');
// const log = require(`${library}/logger`);
var api = new API();
var api = global.yoho.API;
/**
* 商品尺码信息处理
... ... @@ -273,11 +270,11 @@ const getSizeInfo = (sizeInfo) => {
module.exports = (data) => {
var finalResult;
return api.get('', sign.apiSign({
return api.get('', {
method: 'h5.product.intro',
productskn: data.productskn,
udid: 'f528764d624db129b32c21fbca0cb8d6'
})).then(result => {
}).then(result => {
finalResult = getSizeInfo(result);
return finalResult;
... ...
/**
* 频道页面 model
* @author: 赵彪<bill.zhao@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
const utils = '../../../utils';
const contentCodeConfig = require('../../../config/content-code');
const _ = require('lodash');
const sign = global.yoho.sign;
const camelCase = global.yoho.camelCase;
const log = global.yoho.logger;
const serviceApi = global.yoho.ServiceAPI;
const api = global.yoho.API;
const resourcesProcess = require(`${utils}/resources-process`);
const dateFormate = (str) =>{
var time = new Date(str * 1000);
var y = time.getFullYear();
var m = time.getMonth() + 1;
var d = time.getDate();
var h = time.getHours();
return y + '年' + m + '月' + d + '日' + h + '时';
};
// 为了活动卡片特殊样式,将折扣信息拆分开来
const _transDiscountToArr = (discount) => {
return discount.replace(/(?:\d+[.\d]?)([\u4e00-\u9fa5]{1})/g, function(fullMatch, capture) {
if (capture) {
const arr = [];
arr.push(fullMatch.replace(capture, ''));
arr.push(capture);
return arr;
} else {
return fullMatch;
}
}).split(',');
};
/**
* 获取资源位
* @param {String} channel 频道
* @param {String} contentcode 内容码
* @return {Promise}
*/
const _getOutletResource = (channel, contentcode) => {
const params = {
content_code: contentcode || contentCodeConfig.outlet,
limit: 25,
yh_channel: channel || ''
};
return serviceApi.get('operations/api/v5/resource/home', sign.apiSign(params)).then(result => {
if (result && result.code === 200) {
return resourcesProcess(result.data.list);
} else {
log.error('the response code of outlet "operations/api/v5/resource/home" is NOT 200', result);
return [];
}
});
};
/**
* 转换导航数据
* @param {[Object]} 原始导航数据
* @return {Object} 转换后的数据
*/
const _convertNavData = (list) => {
const formatData = [];
list = list || [];
list = camelCase(list);
_.forEach(list, (item) => {
formatData.push({
id: item.id,
name: item.sortName,
url: encodeURI(item.sortUrl)
});
});
return {data: formatData};
};
/**
* 获取导航数据
* @param {String} 导航类型id
* @return {Promise}
*/
const _getNavData = (categoryId) => {
const params = {
parent_id: categoryId
};
return serviceApi.get('operations/api/v6/category/getCategory', sign.apiSign(params)).then(result => {
if (result && result.code === 200) {
let data = _convertNavData(result.data);
data.category = categoryId;
return data;
} else {
log.error('the response code of "operations/api/v6/category/getCategory" is NOT 200', result);
return [];
}
});
};
/**
* 转换奥莱活动数据
* @param {Object} data 原始数据
* @return {Object} 转换后的数据
*/
const _convertActicityData = (data) => {
const formatData = [];
let discountArr = [],
discountNum = 0,
discountText = 0;
data = data || [];
_.forEach(data, (item) => {
discountArr = item.promotionName.split('~');
if (discountArr.length === 1) {
discountNum = _transDiscountToArr(discountArr[0])[0];
discountText = _transDiscountToArr(discountArr[0])[1];
} else {
discountNum = discountArr[0] + '~' + _transDiscountToArr(discountArr[1])[0];
discountText = _transDiscountToArr(discountArr[1])[1];
}
formatData.push({
activityUrl: '/product/outlet/activity?id=' + item.id,
coverUrl: item.coverUrl,
logoUrl: item.logoUrl,
title: item.title,
discountNum: discountNum,
discountText: discountText,
productPoolId: item.productPoolId || '',
leftTime: item.startLeftTime > 0 ? dateFormate(item.startTime) : item.endLeftTime,
hide: false
});
});
return formatData;
};
/**
* 获取奥莱活动详情
* @param {String} id 活动id
* @return {Promise} 调用接口的Promise
*/
const _getActivityDetail = (id) => {
var params = {
method: 'app.outlets.activityGet',
sort: 1, // 接口规定传1
platform: 3, // h5平台代号
id: id,
type: 0 // 接口规定传0
};
return api.get('', sign.apiSign(params)).then(res => {
if (res.code === 200) {
return _convertActicityData(res.data);
} else {
log.error('the response code of "app.outlets.activityGet" is NOT 200', res);
return {};
}
});
};
/**
* 获取奥莱资频道页活动列表
* @param {Object} data 请求接口所需的参数
* @return {Promise} 调用接口的Promise
*/
const _getHomeActivity = (data) => {
var params = {
method: 'app.outlets.activityGet',
platform: 3 // h5平台代号
};
return api.get('', sign.apiSign(_.assign(params, data))).then(res => {
return _convertActicityData(res.data);
});
};
/**
* 获取奥莱资首页内容
* @param {String} categoryId 父级菜单id,用于标明当前页面是奥莱页面
* @param {Strting} channel 奥莱频道
* @param {Strting} code 内容码
* @return {Promise} 调用接口的Promise
*/
const getContent = (categoryId, channel, code) => {
let params = {
type: 0, // 获取全部奥莱活动列表, 不区分是否将开始或结束
yh_channel: channel
};
const p = [_getNavData(categoryId), _getOutletResource(channel, code), _getHomeActivity(params)];
return Promise.all(p).then(data => {
return {
nav: data[0] || [],
content: data[1] || [],
activity: data[2]
};
});
};
/**
* 获取奥莱活动详情
* @param {String} id 活动id
* @return {Promise} 调用接口的Promise
*/
const getActivity = (id) => {
return _getActivityDetail(id).then(res => {
return {
activity: res,
productPool: res[0] && res[0].productPoolId || '',
activityTitle: res[0] && res[0].title || 'OUTLET',
saleType: 4 // 促销类型, 奥莱为4
};
});
};
/**
* 获取即将开始或即将结束的活动列表
* @param {Number} type 标明是上线预告还是即将结束
* @param {String} categoryId 父级菜单id,用于标明当前页面是奥莱页面
* @return {Object} 活动列表数据
*/
const getRecentActivity = (type, categoryId) => {
var params = {
type: type
};
return Promise.all([_getNavData(categoryId), _getHomeActivity(params)]).then(res => {
return {
nav: res[0] || [],
activity: res[1]
};
});
};
module.exports = {
getContent: getContent,
getActivity: getActivity,
getRecentActivity: getRecentActivity
};
... ...
... ... @@ -7,13 +7,11 @@
'use strict';
const library = '../../../library';
const utils = '../../../utils';
const API = require(`${library}/api`).API;
const sign = require(`${library}/sign`);
const api = global.yoho.API;
const sign = global.yoho.sign;
const _ = require('lodash');
const productProcess = require(`${utils}/product-process`);
var api = new API();
module.exports = (data) => {
var finalResult;
... ...