一、前言
任何一个电商网站现在都会有全局搜索商品的功能,或者是模糊搜下,本篇文章从代码上分析搜索如何做。
二、代码分析
我们直接看代码,看看都经过了那些查询和步骤
搜索商品调用的是getAppProSearchList这个方法,我们看一下传参和返回结果格式。
传参:searchModel,这里其实做了封装,我们暂时不用管他,就当我们这个参数能接受到前端传的字符串就行、
public String getReqBody() {
if (getRequest() != null && getRequest().getParameter("jsonData") != null) {
return StringEscapeUtils.unescapeHtml4(getRequest().getParameter("jsonData"));
}
return "";
}
返回结果数据结构:ResponseProSearchModel,里面的字段就比较丰富,就不贴源码了,大概得字段包含这些
1、查询商品活动相关的信息
//已删除、未启用的活动不返回商品
if (getSearchModel().getActivityId() != null) {
MktActivity mktActivity = MktActivityDao.getMktActivityByCode(getSearchModel().getActivityId());
resp.setActivityTag(mktActivity.getActivityTag());
resp.setActivityPromotionalPic(mktActivity.getActivityPromotionalPic());
if (mktActivity != null && (mktActivity.getState() == MktActivity.STATE_DELETED || mktActivity.getState() == MktActivity.STATE_NONE_ENABLE || mktActivity.getEndDate().compareTo(new Date()) < 0)) {
return resp;
}
if (Arrays.asList(MktPromotePointsExchangeModel.TAG_COUPON,
MktPromotePointsExchangeModel.TAG_JINODU_EX_COUPON,
MktPromotePointsExchangeModel.TAG_REWARD_EX_COUPON,
MktPromotePointsExchangeModel.TAG_YOUDOU_EX_COUPON,
MktPromotePointsExchangeModel.TAG_CHAIYOUDOU_EX_COUPON,
MktPromotePointsExchangeModel.TAG_YOUJIN_EX_COUPON).contains(mktActivity.getActivityTag())) {
if (StringUtils.isEmpty(getSearchModel().getQueryType())) {
getSearchModel().setQueryType(QJS);
}
getSearchModel().setActivityTag(mktActivity.getActivityTag());
}
}
2、查询SKU相关信息
//REMARK: 配送方式:是区域配送还是全国配送
ProSkuSale skuSale = new ProSkuSale();
skuSale.setSupportCountryDelivery(ProSkuSale.SUPPORT_COUNTRY_DELIVERY_YES);//默认查询商品支持全国配送
skuSale.setSupportRetail(ProSkuSale.TRUE);//默认查询商品支持零售
skuSale.setProListIsShow(ProSkuSale.TRUE);//商城列表可售
if (null != getSearchModel().getSupportCountryDelivery() && getSearchModel().getSupportCountryDelivery() == ProSkuSale.SUPPORT_COUNTRY_DELIVERY_NO) {
//同城配送入口 查询的商品不支持全国配送 根据定位的城市 查询商户 加载商户下的商品
logger.info("同城配送入口-start");
skuSale.setSupportCountryDelivery(ProSkuSale.SUPPORT_COUNTRY_DELIVERY_NO);
if (getSearchModel().getArea() == null || null == getSearchModel().getArea().getCityId()) {
logger.info("同城送未传入当前定位的城市,返回商品为空");
return resp;
}
//加载满足的商户 市 区
List<String> mchOuCodeList = new ArrayList<>();
List<String> mchOuCodeListCity = EtpMerchantDao.getMerchantCodeByArea(getSearchModel().getArea().getCityId());
if (null != mchOuCodeListCity)
mchOuCodeList.addAll(mchOuCodeListCity);
if (CollectionUtils.isEmpty(mchOuCodeList)) {
logger.info("查询根据市区查询商户列表返回为空");
return resp;
}
String mchCode = StringUtils.join(mchOuCodeList.toArray(), ",");
logger.info(String.format("查询根据省市查询商户列表返回结果=[%s]", mchCode));
//拼接sql
skuSale.appendSql(" and mch_ou_code in (" + mchCode + ") ");
getSearchModel().setMchCodeStr(mchCode);//REDIS key
}
3、我们看看SKU一般有哪些字段,也是比较丰富。
/**
* 在售商品状态 0下架 1正常 2淘汰'
*/
private Integer status;
private Integer saleNum;
//新加字段
private Integer comPolicyId; // 基础价格政策id
private Integer spePolicyId; // 特殊价格政策id
private String mobileDetail; // 移动端详情
private String pcDetail; // pc端详情
private String mobileParam; // 移动端规格
private String pcParam; // pc端规格
private String afterHelp; // 售后说明
private String pcAfterHelp; // pc端维修售后情况
private String warePack; // 售后包装
private Integer isDel;
private Integer interfaceType; // 接口类型
private BigDecimal onlineSalePrice; // 线上销售价(启用线上销售时有效)
private Integer enableOnlineSale; // 是否支持线上售卖(0:不支持线上售卖,1支持线上售卖,线下商品默认0)
private BigDecimal offlineSalePrice; // 线下销售价(启用线下销售时有效)
private Integer isDistribution;//是否分销(否:0,是:1)
private Integer recommendPro;//是否是推荐商品 1 是 0 否
private Integer rewardType;//奖励类型 1:金额 2:积分 3:油滴 4:豌豆
private BigDecimal rebate;//分佣返点
private Integer packageItemNum;
private Date distributionTime;//商品分销上架时间
private Integer rebateModel;//微店返利模式 字典表 microstore_rebate_model 配置
/**
* 商品包生效日期
*/
private Date packageStartTime;
/**
* 商品包失效日期
*/
private Date packageEndTime;
private String remark;
/**
* 商品条码
*/
private String barcode;
/**
* 销项税率
*/
private BigDecimal taxSaleRate;
private String mncode;
/**
* 单位
*/
private String unit;
/**
* 包装单位
*/
private String packUnit;
/**
* 包装细数
*/
private String packSize;
/**
* 包装细数
*/
private Integer packNumber;
/**
* 单件件数
*/
private Integer singleNumber;
/**
* 库存管理方式(1-进销存 2-平台管库存 3-不管库存 4-第三方接口管库存 5管理库存并且允许负库存销售)
*/
private Integer stockMode;
/**
* 扩展 销售价格范围
*/
private String salePriceRange;
/**
* 扩展 搜索关键字
*/
private String keyword;
private String metric;//度量单位(由数据字典配置 直接存名称)
/**
* 扩展:配送地址-省名称
*/
private String provinceName;
/**
* 扩展:属性id 2 为品牌 其他为属性规格
*/
private Integer featureId;
private Integer creatorId;
private String creator;
private Date createTime;
private Integer modifierId;
private String modifier;
private Date modifierTime;
private BigDecimal weight;
private BigDecimal volume;
private Long freightTemplId;
private String eleType;
private String eleTypeName;
private String featureName;
/**
* 是否转换单位
*/
private Integer isConvertUnit;
/**
* 原单位
*/
private String originalUnit;
/**
* 转换系数
*/
private BigDecimal conversionScale;
/**
* 转换前单位
*/
private BigDecimal priceBeforeConversion;
/**
* 扩展:配送地址-市名称
*/
private String cityName;
/**
* 扩展:配送地址-区/县(三级地址)名称
*/
private String countryName;
private Integer initStock;
private String creatorOuCode;//创建人组织机构
/**
* 扩展 App 排序model
*/
private SortModel sort;
/**
* 扩展 品牌id 集合
*/
private List<Integer> brandList;
/**
* 扩展 属性值id 集合
*/
private List<Integer> featureIdList;
private ProMerCategory proMerCategory; //关联商品分类
/**
* 总库存
*/
private Long totalStock;
/**
* 剩余库存
*/
private Long surplusStock;
/**
* 锁库存数
*/
private Long lockStock;//冻结库存,下单未支付
private Long minDevNum;
//扩展字段
private String orgCode;//组织机构编码
/**
* 一级分类
*/
private Long cls1StaId;
/**
* 二级分类
*/
private Long cls2StaId;
/**
* 三级分类
*/
private Long cls3StaId;
private Long clsId;
private Date addSkuStoreTime;
private Integer addSkuStockNum;
private Integer isSetSkuStockRule;
private Date stockStartTime;
private Integer stockCalcWay;
private Integer perDaystockNum;
private List<Integer> perWeekstockNums;
private String thirdCode;//供应商商品编码
private Integer saleChannel;
private ProSku proSku;
private ProSpu proSpu;
private String proName;
private BigDecimal price;
private List<String> orderbys;
private Long activityId;
private Integer activityType;
private String area;
private Integer orderByKey;
private Integer ascOrDesc;
private Integer stockGenType;
private String stockGenNum;
private List<Long> featureIds;
private List<Long> brandIds;
private String clsName;
private BigDecimal activityPrice;
/**
* 毛利
*/
private BigDecimal gross;
/**
* 毛利率
*/
private BigDecimal grossRate;
private String pcMainUrl;
private String phMainUrl;
/**
* 收款单位名称
*/
private String receivablesOuName;
/**
* 收款单位机构编码
*/
private String receivablesOuCode;
private Integer saleNumMax;
private Integer saleNumStep;
private Integer saleNumBegin;
private Long policyId;
private Long policyItemId;
private String province;
private String city;
private String county;
private String town;
private String devMode;
private Integer preSaleDays;
private BigDecimal workDayPrice;
private BigDecimal workEndPrice;
private String preSaleDate;
private String saleAreaIds;
/**
* 可售区域范围
*/
private String saleAreaNames;
private Long stockSaleNum;
private Long stockRefundNum;
private Long stockAllotNumber;//出库库存,下单已支付未完成
private Integer userRewardType;//员工分销奖励类型 1:积分 2:返利红包
private BigDecimal employeeRebate;//员工返点比例
private BigDecimal higherRebate;//上级返点比例
private Integer priceManageType;
private Integer supportCountryDelivery;//支持全国配送 0 不支持 1支持 (默认支持)
private Integer shelvesType;//自提店铺上架方式 2-手动处理、1-海信接口 (默认1)3-自动上架
//数量(传值用)
private Integer num;
//商品的总销量
private Integer saleGuCount;
//税收分类编码
private String taxRateCode;
//是否主规格 1是0否
private Integer isMainFeature;
//扩展字段
private String supOuCode;
private String cntId;
private String clsOil;
private Integer devPro;//配送属性(0-直送,1-越库,2-配送)
private BigDecimal withTaxPrice;//扩展含税进价,采购单记账新增商品用
private String unitOuCode;
private List<TSkuGtPluHxModel> tSkuGtPluHxModelList;
private BaseSysStoreModel baseSysStoreModel;
private Integer shopSort;
private String etpSupOuCode;//供应商
private String etpSupOuName;//供应商名称
private String dailySaleBegin;//每日开售开始时间
private String dailySaleEnd;//每日开售结束时间
private String storeParentOuCode;//店铺所属组织机构
private String storeParentOuName;//店铺所属组织机构名称
private Long hotRankingNum;//热推值
private Integer isPickUp;//是否支持自提 1支持 0 不支持
private Integer isExpress;//是否支持快递配送 1支持 0 不支持
private Integer isStoreExpress;//是否支持门店配送 1支持 0 不支持
private Integer skuPopular;//热门商品0:否1:是
private Integer skuPopularSort;//热门商品排序
private Integer needReferrer;//是否必填推荐人 0不必填 1必填 (默认0)
private String eleTypeJson;//电子券json
private Long dayLimitNum;//单日最大销售数量
private Integer memberLimitNum;//每人限购数量
private Integer memberLimitCircle;//每人限购周期 -- 0-每天 1-每月 2-每年 3-终身 null-终身
// private Long proSkuDistributionId;//分销商品表id
private Integer shelvesAllStore;//上下架所有店铺 1-当前店铺 2-所有店铺
// public Long getProSkuDistributionId() {
// return proSkuDistributionId;
// }
//
// public void setProSkuDistributionId(Long proSkuDistributionId) {
// this.proSkuDistributionId = proSkuDistributionId;
// }
private String etpSupHxCode;//供应商海信编码
private Integer supportCancelOrder;//支持取消订单 1-支持 0-不支持 默认1
private String approvalBizLine;//审核业务线条
private Date pickUpStartDays;//提货开始时间
private Date pickUpEndDays;//提货结束时间
private Integer supportRetail;//是否支持零售 0不支持 1 支持
private String serviceInfo;//商品相关服务
private Integer supportPreSale;//是否预售 1-是预售 0-不预售 默认0
private Integer thirdServeId;//第三方服务关联表id
private Integer filterType; // 0, 综合排序,1,销量升序,2,销量降序,3,价格升序,4,价格降序
private BigDecimal maxPrice; // 最大价格
private Integer cateLevel; // 商品分类级别
private Integer searchBySpu; // 根据SPU进行搜索
//20200513增加重点商品标识
private String keyCommodity;//1:重点商品,0:非重点商品
private String skuIdsStr;
private String sysAcCode;//自建为空,海信同步:His,餐饮:Food,汽服:QF
private String noilCode;//便利店标准编码
private String uniCode;//20200605冗余字段
private Integer isSyncHx;//是否同步海信1同步0不同步 默认同步
private String supportRetailStr;//是否支持零售 0不支持 1 支持
private String needReferrerStr;//是否必填推荐人 0不必填 1必填 (默认0)
private String supportPreSaleStr;//是否预售 1-是预售 0-不预售 默认0
private Integer limitLiveSale;//限直播购买 0不限制(默认) 1仅限直播购买
private Date soldOutTiming;//直播商品定时下架时间
/**
* 大屏相关字段
*/
private Integer isSupBigScreen; // 是否支持大屏(大屏购物)
private String bigScreenCreator; // 大屏购物创建人
private Long bigScreenCreatorId; // 大屏购物创建人
private Date bigScreenCreateTime; // 大屏购物创建时间
private String bigScreenCreateTimeStr;
private String idStr;
private List<Integer> ids;
private String standardCode;//油站标准编码
private BigDecimal pluid;//海信库商品pluid
//专属活动类型
private String excluActType;
//专属活动ID
private String excluActId;
// 牵牛花添加
private String isms;//是否免税
private String glorySpec;//规格
private String ch21;//海关21大类代码
private String ch21Name;// 海关21大类中文名称
private String brandThirdCode; // 商品第三方品牌编码
private String thirdStaCode; // 商品第三方分类编码(EOP)编码
private String chTax;//海关税类
private BigDecimal pTaxRate;//行邮税
private String xstaxtype;//销售税种
private BigDecimal xftaxrate;//消费税率
private BigDecimal noTaxCostPrice;//不含税进价
private Integer enableSaleMax;//商品最大可售数量
private Integer isHasStock; //是否有库存 1有0无
private Integer eopPackage;
private Integer givePoints;//是否送积分
private Integer saleMold;//销售类型 普通 预售 代发
private String customsMinUnit; //对应海关最小单位
private BigDecimal barcodePrice; //条形码价
private BigDecimal vatRate; //增值税率
private String thirdCategroyCode; //第三方分类编码
// 微店
private Long microStoreProId;//微店商品表id
private Long proSkuDistributionId; //分销商品Id
private Integer isRecommend; //是否是推荐商品 0 不是 1 是
private Integer isRecommendEmp;//员工特推 1 是 0 否
private String microStoreCode;//微店编码
private String skuCodeListStr;//商品編碼List
private String idList;//IdList
private Integer isShowAfterSale;//是否展示售后介绍1展示0不展示默认展示
//预售新增字段
private Date preSaleBeginTime; //预售开始时间
private Date preSaleEndTime; //预售结束时间
private Date beginPickUpTime; //开始提货时间
private Integer enableSaleMaxOriginal;//商品最大可售数量预售前
private Integer memberLimitNumOriginal;//每人限购数量预售前
private Integer storePreSaleNum; //门店预售库存数量
private Integer preSaleEndDealType;//预售结束处理方式 0直接下架1正常售卖
private Date preSaleCreateTime;//预售商品添加时间
/**
* 预售状态 为了方便页面筛选数据 默认为0
* sku被选为预售商品后,所有门店数据该值为1 表示预选为预售商品
* 当对应门店选为预售商品后 该值为2
* 预售sku删除之后 该值为0
* 预售门店删除之后 该值为1
* <p>
* 定时任务处理也是根据该字段 用来更新SupportPreSale
* 预售开始 SupportPreSale=1 该值为3
* 预售期结束SupportPreSale=0 该值为4
*/
private Integer preSaleState;//0非预售1待选预售2未开始预售3预售中4已结束
private Integer stockNumOriginal;//预售前库存数量
private Integer isShowPromotePrice;//是否显示促销价格
/**
* 百望商品编码
*/
private String bwGoodsCode;
private String shelveChannels;//上架渠道-从合作渠道选择
private String shelveChannelNames;//上架渠道-从合作渠道选择
private Integer proListIsShow;//商品列表是否展示 1-展示0-不展示 默认展示
private String keyword1;//查询参数
private String onlineTimeStr;//上架查询时间
private String exchangeJumpAddress;//卡兑换调转地址
private Integer enabled; //是否可用(0-启用 1-不启用)
private Integer isSellOut; //已售罄 查询参数
private Integer sendGift; //'0:不参与送礼,1:参与送礼'
private BigDecimal serveHour; //服务时长
private String accessoriesCategoryCode;//配件品类id
private String accessoriesCategoryName;//配件品类名称
private Integer isSupportInstall;//是否支持安装
private String vehicleGroupCodes;//车辆组编码,以英文,分割 1001,1002
private String vehicleGroupStr;//车辆组展示字符串
private String materialCode;//物料编码
private Integer configType;//配置类型
private String assistUnit;//辅助单位
private Long assistUnitCount;//辅助单位数量
private String materialName;//物料名称
//电子券虚拟商品相关
private Integer couponCodeGenerateType; //券码生成方式0-随机生成券码
private Integer validityPeriodType; //有效期分类0-长期有效1-相对日期有效2-绝对日期有效
private Date beginTime; //有效起始日期
private Date endTime; //有效结束日期
private String effectiveDuration; //相对日期有效,有效时长
private String applyStoreCodes; //适用门店编码","分割
private String applyStoreNames; //适用门店名称","分割
private Integer applyStoreType; //适用门店类型0-所有店铺1-指定店铺
private Long storeCls1StaId; //店铺一级分类id
private Long storeCls2StaId; //店铺二级分类id
private String storeCategoryName; //店铺分类名称
private String storeCategoryFullName; //店铺分类全称
按照这个逻辑下来,其实就是查询拼接出一个共同的ResponseProSearchModel
三、电商商品全局搜索方案
电商商品全局搜索方案是一个复杂且关键的系统设计,旨在为用户提供高效、准确的商品搜索体验。以下是一个全局搜索方案的简要概述:
一、需求分析
首先,需要深入了解电商平台的业务需求和用户搜索习惯。分析用户搜索行为、关键词使用频率以及搜索结果的满意度,以便为搜索方案的设计提供有力的依据。
二、搜索词库建立与维护
- 根据用户的搜索日志、品牌名称、属性、类目等信息,建立关键词搜索词库。
- 定期对搜索词库进行维护和更新,确保词库的准确性和完整性。
- 利用分词技术对用户输入的关键词进行拆分,以便更精确地匹配商品信息。
三、搜索算法优化
- 基于关键词匹配:采用先进的搜索引擎算法,根据用户输入的关键词从商品库中检索相关商品信息。
- 相关性排序:通过分析用户的搜索意图、商品的相关性、销售情况和用户评价等因素,对搜索结果进行排序,确保最匹配的商品排在前面。
- 语义理解:利用自然语言处理技术,对用户输入的关键词进行语义理解,提高搜索的准确性和智能性。
四、个性化推荐
- 根据用户的搜索历史、购买记录等信息,为用户推荐相关商品,提高购物的便捷性和满意度。
- 结合用户的地理位置、时间等因素,为用户提供更精准的本地化推荐。
五、搜索界面优化
- 设计简洁明了的搜索界面,方便用户输入关键词和查看搜索结果。
- 提供搜索联想、关键词纠错等功能,帮助用户更快速地找到想要的商品。
- 优化搜索结果的展示方式,如使用图片、价格、销量等信息,提高用户的浏览体验。
六、性能优化
- 提高搜索系统的响应速度,确保用户能够快速得到搜索结果。
- 优化数据库查询性能,减少数据查询的延迟。
- 利用缓存技术,提高重复搜索的响应速度。
七、数据监控与分析
- 对搜索系统的数据进行监控,包括搜索量、点击量、转化率等指标。
- 利用数据分析工具,对搜索效果进行定期评估和优化,确保搜索方案始终符合用户需求和市场变化。
四、springboot使用ES全局检索
也可以使用ES做全局搜索。
在Spring Boot中使用Elasticsearch(ES)进行全局检索,你可以通过Spring Data Elasticsearch模块来简化操作。以下是一个简单的步骤指南,帮助你实现Spring Boot应用程序中的全局检索功能:
1. 添加依赖
首先,在你的pom.xml
文件中添加Spring Data Elasticsearch的依赖:
<dependencies>
<!-- Spring Boot Starter Data Elasticsearch -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- 其他依赖 -->
</dependencies>
确保你的Spring Boot版本与Spring Data Elasticsearch版本兼容。
2. 配置Elasticsearch
在application.properties
或application.yml
中配置Elasticsearch连接信息:
# application.properties
spring.data.elasticsearch.cluster-nodes=localhost:9300
spring.data.elasticsearch.cluster-name=your_cluster_name
3. 创建实体类
创建一个与Elasticsearch索引中的文档相对应的实体类。使用@Document
注解来标记类,并使用@Field
注解来标记字段。
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@Document(indexName = "your_index_name")
public class YourEntity {
@Id
private String id;
@Field(type = FieldType.Text, fielddata = true)
private String title;
@Field(type = FieldType.Text, fielddata = true)
private String description;
// 其他字段和getter/setter方法
}
4. 创建Repository接口
创建一个继承自ElasticsearchRepository
的接口,用于执行Elasticsearch操作。
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface YourEntityRepository extends ElasticsearchRepository<YourEntity, String> {
// 自定义查询方法(如果需要)
}
5. 创建全局检索服务
创建一个服务类来封装全局检索的逻辑。你可以使用ElasticsearchRestTemplate
来执行复杂的查询。
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class GlobalSearchService {
@Autowired
private ElasticsearchRestTemplate elasticsearchTemplate;
public List<YourEntity> search(String query) {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("title", query))
.withQuery(QueryBuilders.matchQuery("description", query))
.build();
SearchHits<YourEntity> searchHits = elasticsearchTemplate.search(searchQuery, YourEntity.class);
return searchHits.get().getContent();
}
}
6. 使用服务
最后,在你的控制器或其他业务逻辑中调用全局搜索服务。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class SearchController {
@Autowired
private GlobalSearchService globalSearchService;
@GetMapping("/search")
public List<YourEntity> search(@RequestParam String query) {
return globalSearchService.search(query);
}
}