/** * search * @Author: sefon 2016-7-12 16:31:56 */ 'use strict'; const utils = '../../../utils'; const Promise = require('bluebird'); const co = Promise.coroutine; const helpers = global.yoho.helpers; const redis = global.yoho.redis; const SaleApiModel = require('./sale-api'); const SearchApiModel = require('./search-api'); const HeaderModel = require('../../../doraemon/models/header'); const productProcess = require(`${utils}/product-process-simple`); const searchHandler = require('./search-handler'); const hotBrandsModel = require('../../../doraemon/models/hot-brands'); const _ = require('lodash'); const Fn = require('lodash/fp'); const md5 = require('md5'); const cache = global.yoho.cache; const config = global.yoho.config; const logger = global.yoho.logger; const needParams = ['query', 'msort', 'misort']; const CACHE_TIME_S = 60; function _getCacheKey(params, page) { let sortByKey = Fn.pipe(Fn.toPairs, Fn.sortBy(0), Fn.flatten); let genKey = Fn.pipe(Fn.cloneDeep, sortByKey, Fn.join('_')); return `render_cache_${page}_${md5(genKey(params))}`; } function getKeyActivity(query) { return this.searchApi.getKeyActivityAsync(query).then(result => { return _.get(result, 'data.urlobj.pcUrl', ''); }); } const _setSearchData = (result, params, channel) => { let changeQuery = Object.assign({}, params); let finalResult = { headerData: Object.assign(result[0].headerData, { header: true }), search: {} }; // 获取左侧类目数据 if (result[1].code === 200) { let dps = {}; _.forEach(needParams, (value) => { if (params[value]) { dps[value] = params[value]; } }); finalResult.search = { leftContent: searchHandler.handleSortData(result[1].data, dps, params), isSearch: true }; } // 获取商品数据和顶部筛选条件 if (result[2].code === 200) { // 搜索推荐词,获取对应的参数值 let termsSuggestion = _.get(result[2], 'data.suggestion.terms_suggestion', []); // 推荐词 获取无商品,首次搜索,通过isChangedQuery做开关 if (result[2].data.isChangedQuery === 'Y') { Object.assign(changeQuery, {query: termsSuggestion[0]}); } let filters = Object.assign(searchHandler.handleFilterDataAll(result[2].data, changeQuery), _.get(finalResult, 'search.leftContent.sort', {})); filters.checkedConditions.conditions = _.concat(filters.checkedConditions.conditions, _.get(finalResult, 'search.leftContent.checked', [])); Object.assign(finalResult.search, searchHandler.handlePathNavData({total: result[2].data.total}, params, 'search', channel), { filters: filters, opts: searchHandler.handleOptsData(changeQuery, result[2].data.total, result[2].data.filter), totalCount: result[2].data.total, footPager: searchHandler.handlePagerData(result[2].data.total, changeQuery), goods: productProcess.processProductList(result[2].data.product_list, Object.assign({showDiscount: false, from: {type: 'search', params: params}}, params)), latestWalk: 6, hasNextPage: searchHandler.handleNextPage(changeQuery, result[2].data.total), shopEntry: result[3], name: params.query, suggest: (termsSuggestion.length !== 0) ? termsSuggestion.slice(0, 3) : false, changedQuery: result[2].data.isChangedQuery === 'Y', suggestFirst: finalResult.search.suggestFirst, suggestSecond: finalResult.search.suggestSecond }); if (finalResult.search.changedQuery) { finalResult.search.suggestFirst = finalResult.search.suggest[0]; finalResult.search.suggestSecond = _.slice(termsSuggestion, 1, termsSuggestion.length).splice(0, 3); } finalResult.criteo = {skn: searchHandler.getCriteo(_.get(finalResult.search, 'goods'))}; } return finalResult; }; /** * 转换价格 * * @param float|string $price 价格 * @return float|string 转换之后的价格 */ const _transPrice = (price) => { return price ? (price * 1).toFixed(2) : '0.00'; }; /** * 获取搜索数据 * @param {[type]} origin [description] * @return {[type]} [description] */ function getSearchData(params, channel) { let searchParams = searchHandler.getSearchParams(params); switch (channel) { case 'boys': searchParams.physical_channel = 1; break; case 'girls': searchParams.physical_channel = 2; break; case 'kids': searchParams.physical_channel = 3; break; case 'lifestyle': searchParams.physical_channel = 4; break; default: break; } // 调用接口 let apiMethod = [ this.headerModel.requestHeaderData(channel), this.searchApi.getSortList(Object.assign({}, searchParams, {msort: '', misort: '', sort: ''})), this.searchApi.getProductList(searchParams, 'fuzzySearch'), this.searchApi.getShopList(params) ]; return Promise.all(apiMethod).then(result => { if (_.get(result, '[2].data.isChangedQuery', '') === 'Y') { return this.searchApi.getSortList(Object.assign({}, searchParams, { msort: '', query: _.get(result, '[2]data.suggestion.terms_suggestion[0]', ''), misort: '', sort: '' })).then(subRes => { result[1] = subRes; return _setSearchData(result, params, channel); }); } return _setSearchData(result, params, channel); }); } function getSearchDataPre(params, channel) { let cKey = _getCacheKey(Object.assign({channel: channel}, params), 'search'); let getOriginData = () => { return this.getSearchData(params, channel).then(result => { // 查询结果为空则不cache if (config.useCache && !_.isEmpty(_.get(result, 'search.goods', []))) { cache.set(cKey, result, CACHE_TIME_S) .catch(err => logger.debug(`search render cache data save fail:${err.toString()}`)); } return result; }); }; if (config.useCache) { return cache.get(cKey).catch(err => { logger.debug(`get search render cache data fail:${err.toString()}`); return getOriginData(); }).then(cdata => { let hasCache = false; if (cdata) { try { cdata = JSON.parse(cdata); hasCache = true; } catch (e) { logger.debug('search render cache data parse fail.'); } } if (hasCache) { return cdata; } else { return getOriginData(); } }); } return getOriginData(); } /** * 搜索提示 */ function getSuggest(params) { return this.searchApi.getSuggest(params).then(result => { let dest = []; if (result.code === 200) { dest = searchHandler.handleSuggest(result.data); } return dest; }); } function getListBrandsFilter(params, nparams) { return this.searchApi.getBrands4Filter(Object.assign({}, params, nparams)).then(result => { let dest = []; if (result.code === 200) { dest = searchHandler.handleFilterBrands(result.data, params); } return dest; }); } function getBrands4Filter(params) { let nparams = {}; if (params && params.price) { let mp = params.price.split(','); let nmp = []; nmp[0] = (mp && mp[0]) || 0; nmp[1] = (mp && mp[1]) || 99999; nparams.price = nmp.join(','); } if (params && params.from) { nparams.from = params.from; delete params.from; } // 生成路径包含callback 导致多次调用多个 callback问题 if (params && params.callback) { delete params.callback; } const specialId = parseInt(`0${params.specialsale_id || ''}`, 10); if (specialId) { return this.saleApi.getSaleSpecialAsync(specialId).then(special => { if (special && special.data) { let data = special.data; // 传品牌ID参数 if (data.brand_id) { nparams.brand = data.brand_id; } // 传促销id,促销id为空时传专区id if (data.ispromotion) { nparams.promotion = data.ispromotion; } } return this.getListBrandsFilter(params, nparams); }); } // 商品详情页促销跳转 if (params && params.psp_id) { Object.assign(nparams, { promotion_id: params.psp_id, method: 'app.promotion.aggBrand' }); } // 优惠券商品列表 if (params && params.cpc_id && params.coupon_code) { Object.assign(nparams, { coupon_id: params.cpc_id, coupon_code: params.coupon_code, method: 'app.coupon.aggBrand' }); } return this.getListBrandsFilter(params, nparams); } /** * 搜索少或无 可能喜欢 * @param int $page 分页页码 * @return array * */ function getSearchLessProduct(channelNum, uid, udid, page) { return co(function* () { let result = { code: 200, message: '', data: { header: '可能喜欢的商品', hasPrev: false, hasNext: false, item: null } }; if (!_.isNumber(page)) { return result; } let together = yield this.searchApi.lessRecommend(channelNum, uid, udid, '100003', 20); if (page > 5) { page = 1; } if (!_.get(together, 'data.product_list')) { return result; } result.data.hasPrev = true; result.data.hasNext = true; result.data.item = _.map(together.data.product_list, it => { let item = { id: it.product_id, skn: it.product_skn, title: it.product_name, img: it.default_images ? helpers.image(it.default_images, 180, 240) : '', alt: it.product_name, price: '¥' + _transPrice(it.sales_price), marketPrice: _transPrice(it.market_price) <= _transPrice(it.sales_price) ? false : ('¥' + _transPrice(it.market_price)) }; item.href = helpers.getUrlBySkc(it.product_skn); return item; }); // 当数据量不足6个时,判定为没有下一页 if (page === 1) { result.data.hasPrev = false; } if (!result.data.item || result.data.item.length < 6) { result.data.hasNext = false; } return result; }).bind(this)(); } const _setSearchKeywordData = (result, params, channel) => { let changeQuery = Object.assign({}, params); let finalResult = { headerData: Object.assign(result[0].headerData, { header: true }), search: {} }; _.unset(changeQuery, 'query'); // 获取左侧类目数据 if (result[1].code === 200) { finalResult.search = { leftContent: searchHandler.handleSuggestData(_.get(result, '[1].data', []), params.query), isSearch: true }; } // 获取商品数据和顶部筛选条件 if (result[2].code === 200) { Object.assign(finalResult.search, searchHandler.handlePathNavData({total: result[2].data.total}, params, 'search', channel), { opts: searchHandler.handleOptsData(changeQuery, result[2].data.total), totalCount: result[2].data.total, footPager: searchHandler.handlePagerData(result[2].data.total, changeQuery), goods: productProcess.processProductList(result[2].data.product_list, Object.assign({showDiscount: false, from: {type: 'search', params: params}}, params)), latestWalk: 6, hasNextPage: searchHandler.handleNextPage(changeQuery, result[2].data.total), name: params.query }); finalResult.criteo = {skn: searchHandler.getCriteo(_.get(finalResult.search, 'goods'))}; } return finalResult; }; /** * 获取搜索建议数据 * @param {[type]} origin [description] * @return {[type]} [description] */ function getSearchKeywordData(params, channel) { let searchParams = searchHandler.getSearchParams(params); switch (channel) { case 'boys': searchParams.physical_channel = 1; break; case 'girls': searchParams.physical_channel = 2; break; case 'kids': searchParams.physical_channel = 3; break; case 'lifestyle': searchParams.physical_channel = 4; break; default: break; } searchParams.need_filter = 'no'; // 调用接口 let apiMethod = [ this.headerModel.requestHeaderData(channel), this.searchApi.getSuggest({keyword: searchParams.query.substring(0, 2)}), this.searchApi.getSeoProductList(searchParams, 'fuzzySearch') ]; return Promise.all(apiMethod).then(result => { return _setSearchKeywordData(result, params, channel); }); } /** * 获取搜索建议数据 * @id {[number]} origin [description] * @param {[object]} origin [description] * @channel {[string]} origin [description] * @return {[object]} [description] */ function getSearchKeywordDataById(id, params, channel) { return co(function* () { let redisData = yield redis.all([ ['get', `global:yoho:seo:keywords:id:${id}`] ]); if (!redisData[0]) { return false; } redisData = JSON.parse(redisData[0]); params.query = redisData.name; let resData = yield this.getSearchKeywordData(params, channel); resData.queryKey = params.query; if (!_.isEmpty(redisData.data)) { _.forEach(redisData.data, value => { if (!value) { return; } let href = helpers.urlFormat(`/chanpin/${value.id}.html`, null, 'www'); Object.assign(value, { name: value.keyword, href: href, url: href }); }); let rarr = _.chunk(redisData.data, 12); if (rarr.length) { _.set(resData, 'search.leftContent.allSuggest.list', rarr.shift()); _.set(resData, 'recommendKeywordsInfo', { recommendKeywordsTitle: '更多推荐', recommendKeywords: _.concat(...rarr) }); } } // 前10的热销品牌 Object.assign(resData.search.leftContent, searchHandler.hotBrands(hotBrandsModel.hotBrands().slice(0, 10))); return resData; }).bind(this)(); } module.exports = class extends global.yoho.BaseModel { constructor(ctx) { super(ctx); this.searchApi = new SearchApiModel(ctx); this.saleApi = new SaleApiModel(ctx); this.headerModel = new HeaderModel(ctx); this.getKeyActivity = getKeyActivity.bind(this); this.getSearchData = getSearchData.bind(this); this.getSearchDataPre = getSearchDataPre.bind(this); this.getSuggest = getSuggest.bind(this); this.getListBrandsFilter = getListBrandsFilter.bind(this); this.getBrands4Filter = getBrands4Filter.bind(this); this.getSearchLessProduct = getSearchLessProduct.bind(this); this.getSearchKeywordData = getSearchKeywordData.bind(this); this.getSearchKeywordDataById = getSearchKeywordDataById.bind(this); } };