【商城实战(11)】解锁商品搜索与筛选功能,提升用户购物体验
【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配,乃至运营推广策略,102 章内容层层递进。无论是想深入钻研技术细节,还是探寻商城运营之道,本专栏都能提供从 0 到 1 的系统讲解,助力你打造独具竞争力的电商平台,开启电商实战之旅。
目录
- 一、前端实现:搭建搜索与筛选交互界面
- 1.1 移动前端(uniapp 实现)
- 1.2 PC 前端(Element Plus 实现)
- 二、后端接口开发:构建数据查询桥梁
- 2.1 接口设计与规划
- 2.2 SQL 语句实现基础查询
- 2.3 MyBatis - plus 动态 SQL 优化
- 三、全文搜索技术应用:提升搜索效能
- 3.1 MySQL 全文索引
- 3.1.1 创建全文索引
- 3.1.2 使用 MATCH AGAINST 语句进行全文搜索
- 3.1.3 原理和优势分析
- 3.2 Elasticsearch 应用
- 3.2.1 数据索引到 Elasticsearch
- 3.2.2 使用 Elasticsearch 进行搜索
- 使用 match 查询进行全文搜索
- 使用 bool 查询进行多条件筛选
- 3.2.3 优势分析
一、前端实现:搭建搜索与筛选交互界面
在商城系统中,商品搜索与筛选功能是提升用户购物体验的关键环节。前端作为用户直接交互的界面,其实现的好坏直接影响用户的操作便捷性和满意度。接下来,我们将分别介绍如何在移动前端使用 uniapp,以及在 PC 前端使用 Element Plus 来实现商品搜索框及筛选条件栏。
1.1 移动前端(uniapp 实现)
uniapp 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到 iOS、Android、Web(响应式)、以及各种小程序(微信 / 支付宝 / 百度 / 头条 / QQ / 钉钉)等多个平台。
首先,在 uniapp 项目中创建搜索与筛选页面。在页面的 template 部分,我们使用uni-search-bar组件来创建搜索框,使用view和picker等组件来构建筛选条件栏。以下是核心代码示例:
<template>
<view class="container">
<!-- 搜索框 -->
<uni-search-bar
:show-action="false"
placeholder="搜索商品"
@input="handleSearchInput"
></uni-search-bar>
<!-- 筛选条件栏 -->
<view class="filter">
<view class="filter-item" @click="toggleFilter('category')">
<text>分类</text>
<text>{{ selectedCategory }}</text>
<view class="arrow"></view>
</view>
<view class="filter-item" @click="toggleFilter('price')">
<text>价格</text>
<text>{{ priceRange }}</text>
<view class="arrow"></view>
</view>
<!-- 更多筛选条件可依此结构添加 -->
</view>
<!-- 筛选条件弹出层 -->
<view
class="filter-popup"
v-if="filterVisible"
>
<view v-if="filterType === 'category'">
<picker
mode="selector"
:range="categoryList"
@change="handleCategoryChange"
>
<view class="picker-item">选择分类</view>
</picker>
</view>
<view v-if="filterType === 'price'">
<picker
mode="range"
:range="priceRanges"
@change="handlePriceChange"
>
<view class="picker-item">选择价格范围</view>
</picker>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
searchText: '',
filterVisible: false,
filterType: '',
selectedCategory: '全部',
categoryList: ['全部', '电子产品', '服装', '食品'],
priceRange: '全部',
priceRanges: [
['全部', '0-100', '100-500', '500以上']
]
};
},
methods: {
handleSearchInput(e) {
this.searchText = e.detail.value;
// 这里可以添加发送搜索请求的逻辑
},
toggleFilter(type) {
this.filterType = type;
this.filterVisible = true;
},
handleCategoryChange(e) {
this.selectedCategory = this.categoryList[e.detail.value];
this.filterVisible = false;
// 这里可以添加根据分类筛选商品的逻辑
},
handlePriceChange(e) {
this.priceRange = this.priceRanges[e.detail.value];
this.filterVisible = false;
// 这里可以添加根据价格筛选商品的逻辑
}
}
};
</script>
<style scoped>
.container {
padding: 20rpx;
}
.filter {
display: flex;
justify-content: space-around;
margin-top: 20rpx;
}
.filter-item {
display: flex;
align-items: center;
}
.arrow {
width: 0;
height: 0;
border-left: 6rpx solid transparent;
border-right: 6rpx solid transparent;
border-top: 6rpx solid #000;
margin-left: 10rpx;
}
.filter-popup {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 20rpx;
}
.picker-item {
padding: 15rpx 0;
}
</style>
代码逻辑分析:
- uni-search-bar组件用于创建搜索框,@input事件绑定handleSearchInput方法,当用户输入内容时,将输入值存储到searchText变量中,后续可用于发送搜索请求。
- filter类的view用于包裹筛选条件栏,每个筛选条件项通过点击事件toggleFilter来控制筛选条件弹出层的显示,并根据点击的条件类型设置filterType。
- filter-popup类的view是筛选条件弹出层,根据filterType的值来决定显示不同的筛选内容,如分类选择器或价格范围选择器。
- picker组件用于实现具体的筛选选择功能,通过@change事件绑定相应的处理方法,如handleCategoryChange和handlePriceChange,在方法中更新筛选条件并隐藏弹出层,同时可添加筛选逻辑。
1.2 PC 前端(Element Plus 实现)
Element Plus 是一款基于 Vue 3.0 的桌面端组件库,提供了丰富的组件和功能,方便开发者快速构建美观、高效的 PC 端界面。
在 PC 前端使用 Element Plus 实现商品搜索与筛选功能,我们可以利用el-input组件创建搜索框,使用el-dropdown和el-select等组件实现筛选条件栏。以下是代码示例:
<template>
<div class="search-filter-container">
<!-- 搜索框 -->
<el-input
v-model="searchText"
placeholder="搜索商品"
clearable
@input="handleSearchInput"
></el-input>
<!-- 筛选条件栏 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link">
分类 <i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-for="(category, index) in categoryList"
:key="index"
@click="handleCategoryFilter(category)"
>
{{ category }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-dropdown trigger="click">
<span class="el-dropdown-link">
价格 <i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-for="(range, index) in priceRanges"
:key="index"
@click="handlePriceFilter(range)"
>
{{ range }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 更多筛选条件可依此结构添加 -->
</div>
</template>
<script setup>
import { ref } from 'vue';
const searchText = ref('');
const categoryList = ref(['全部', '电子产品', '服装', '食品']);
const priceRanges = ref(['全部', '0-100', '100-500', '500以上']);
const handleSearchInput = (value) => {
searchText.value = value;
// 这里可以添加发送搜索请求的逻辑
};
const handleCategoryFilter = (category) => {
// 这里可以添加根据分类筛选商品的逻辑
console.log(`筛选分类: ${category}`);
};
const handlePriceFilter = (range) => {
// 这里可以添加根据价格筛选商品的逻辑
console.log(`筛选价格范围: ${range}`);
};
</script>
<style scoped>
.search-filter-container {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.search-filter-container .el-input {
width: 300px;
margin-right: 20px;
}
.el-dropdown-link {
cursor: pointer;
margin-right: 20px;
}
</style>
代码解释:
- el-input组件创建了搜索框,v-model绑定searchText变量,实现输入值的双向绑定,@input事件触发handleSearchInput方法,用于处理搜索逻辑。
- el-dropdown组件结合el-dropdown-menu和el-dropdown-item实现了筛选条件的下拉菜单。例如,通过点击 “分类” 下拉菜单,用户可以选择不同的商品分类,点击事件handleCategoryFilter用于处理分类筛选逻辑。
- 同样,“价格” 筛选条件也是通过类似的结构实现,点击不同的价格范围选项,触发handlePriceFilter方法来处理价格筛选逻辑。
二、后端接口开发:构建数据查询桥梁
前端界面搭建完成后,需要后端接口提供数据支持。后端接口负责接收前端传递的搜索和筛选条件,与数据库进行交互,并返回符合条件的商品数据。
2.1 接口设计与规划
我们使用 Spring Boot 来构建后端接口。接口设计如下:
- 请求方式:使用 GET 请求,符合 RESTful 风格,方便前端调用和数据获取。
- 接口路径:/api/products/search。
- 参数定义:
-
- keyword:字符串类型,用于模糊搜索商品名称,可为空。
-
- category:字符串类型,筛选商品分类,可为空。
-
- minPrice:浮点数类型,筛选商品的最低价格,默认为 0。
-
- maxPrice:浮点数类型,筛选商品的最高价格,默认为一个较大值(如 999999)。
-
- pageNum:整数类型,当前页码,默认为 1。
-
- pageSize:整数类型,每页显示的商品数量,默认为 10。
- 返回数据结构:返回一个包含商品列表和总记录数的 JSON 对象。例如:
{
"total": 100,
"products": [
{
"id": 1,
"name": "商品1",
"category": "电子产品",
"price": 199.99,
"description": "这是商品1的描述"
},
// 更多商品数据
]
}
2.2 SQL 语句实现基础查询
在数据库中,商品信息存储在products表中。以下是使用 SQL 语句实现商品搜索与筛选的示例:
-- 模糊搜索商品名称并进行多条件筛选
SELECT * FROM products
WHERE (name LIKE '%?%' OR? IS NULL)
AND (category =? OR? IS NULL)
AND price BETWEEN? AND?
LIMIT?,?;
解释:
- name LIKE '%?%'实现模糊搜索,?为前端传递的keyword参数。
- (category =? OR? IS NULL)实现分类筛选,如果category参数为空则不进行分类筛选。
- price BETWEEN? AND?实现价格范围筛选。
- LIMIT?,?用于分页,第一个?为(pageNum - 1) * pageSize,第二个?为pageSize。
2.3 MyBatis - plus 动态 SQL 优化
MyBatis - plus 是一个 MyBatis 的增强工具,简化了 SQL 语句的编写和操作。结合 MyBatis - plus 的动态 SQL 功能,我们可以更灵活地构建查询逻辑。
首先,在pom.xml文件中添加 MyBatis - plus 依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>最新版本</version>
</dependency>
然后,定义商品实体类Product和对应的 Mapper 接口ProductMapper。
在ProductMapper.xml文件中,使用 MyBatis - plus 的动态 SQL 标签实现动态查询:
<select id="searchProducts" resultType="Product">
SELECT * FROM products
<where>
<if test="keyword!= null and keyword!= ''">
name LIKE concat('%', #{keyword}, '%')
</if>
<if test="category!= null and category!= ''">
AND category = #{category}
</if>
<if test="minPrice!= null">
AND price >= #{minPrice}
</if>
<if test="maxPrice!= null">
AND price <= #{maxPrice}
</if>
</where>
LIMIT #{(pageNum - 1) * pageSize}, #{pageSize}
</select>
在 Mapper 接口中定义方法:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProductMapper extends BaseMapper<Product> {
List<Product> searchProducts(
@Param("keyword") String keyword,
@Param("category") String category,
@Param("minPrice") Double minPrice,
@Param("maxPrice") Double maxPrice,
@Param("pageNum") Integer pageNum,
@Param("pageSize") Integer pageSize
);
}
在 Service 层调用 Mapper 方法:
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class ProductService {
@Resource
private ProductMapper productMapper;
public IPage<Product> searchProducts(
String keyword,
String category,
Double minPrice,
Double maxPrice,
Integer pageNum,
Integer pageSize
) {
Page<Product> page = new Page<>(pageNum, pageSize);
List<Product> products = productMapper.searchProducts(keyword, category, minPrice, maxPrice, pageNum, pageSize);
page.setRecords(products);
// 这里可以添加查询总记录数的逻辑并设置到page中
return page;
}
}
动态 SQL 在优化查询方面的优势主要体现在:
- 灵活性:根据不同的业务需求和前端传递的参数,动态生成 SQL 语句,避免了编写大量重复的 SQL 代码。例如,当用户只进行搜索而不筛选分类时,不会生成分类筛选的 SQL 片段,提高了查询的针对性。
- 可维护性:将 SQL 语句和 Java 代码分离,使得代码结构更加清晰。当查询逻辑发生变化时,只需要修改 XML 文件中的 SQL 语句,而不需要修改 Java 代码,降低了维护成本。
- 可读性:通过使用等标签,使 SQL 语句的逻辑更加直观,易于理解和调试。
三、全文搜索技术应用:提升搜索效能
随着商城业务的增长和商品数据量的不断增大,传统的 SQL 查询在搜索性能和准确性方面可能会遇到瓶颈。为了提升搜索体验,引入全文搜索技术是一种有效的解决方案。下面将介绍两种常见的全文搜索技术:MySQL 全文索引和 elasticsearch。
3.1 MySQL 全文索引
MySQL 全文索引是一种特殊类型的索引,它允许在文本数据中进行高效的搜索操作。通过为文本数据中的单词和词组创建索引,用户可以快速查找到包含特定词汇的记录。
3.1.1 创建全文索引
在 MySQL 中,并非所有存储引擎都支持全文索引,常用的支持全文索引的存储引擎是 InnoDB(从 MySQL 5.6 版本开始支持)和 MyISAM 。
- 创建表时添加全文索引:
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
description TEXT,
FULLTEXT (name, description)
) ENGINE=InnoDB;
上述代码在products表的name和description字段上创建了全文索引。
- 在已存在的表上添加全文索引:
ALTER TABLE products ADD FULLTEXT (name, description);
此语句为已存在的products表的name和description字段添加全文索引。
3.1.2 使用 MATCH AGAINST 语句进行全文搜索
MySQL 使用MATCH … AGAINST语法进行全文搜索。例如,要查找name或description字段中包含 “手机” 的商品记录,可以使用以下查询:
SELECT * FROM products
WHERE MATCH (name, description) AGAINST ('手机');
这是自然语言模式的搜索,也是默认模式。在自然语言模式下,MySQL 会根据每个单词在整个集合中出现的频率以及它们在给定文档中出现的频率来计算相关性,搜索结果会按照相关性由高到低排序。
另外,MATCH … AGAINST还支持布尔模式,布尔模式提供了更复杂的搜索功能,允许使用操作符如+(必须存在)、-(必须不存在)、*(通配符)等。例如,要查询name字段中必须包含 “苹果” 且不能包含 “华为” 的商品:
SELECT * FROM products
WHERE MATCH (name) AGAINST ('+苹果 -华为' IN BOOLEAN MODE);
3.1.3 原理和优势分析
MySQL 全文索引的原理是通过倒排索引实现的。倒排索引在辅助表中存储了单词与单词在文档中所在位置之间的映射,这样在搜索时可以快速定位到包含特定单词的文档。其优势主要体现在:
- 快速定位:对于大规模文本数据的搜索,能够快速定位到包含关键词的记录,大大提高搜索效率。例如在一个拥有数百万商品记录的商城数据库中,使用全文索引可以在短时间内找到相关商品。
- 语义理解:自然语言模式下,能够根据单词的频率和分布计算相关性,返回更符合用户意图的结果,提升搜索的准确性 。例如用户搜索 “运动鞋”,相关性高的包含 “运动鞋” 的商品会排在前面。
3.2 Elasticsearch 应用
Elasticsearch 是一个基于 Lucene 的开源分布式搜索引擎,它提供了高扩展性、高可用性和高性能的搜索解决方案,在商品搜索中有着广泛的应用。
3.2.1 数据索引到 Elasticsearch
首先,需要将商城的商品数据索引到 Elasticsearch 中。可以使用 Elasticsearch 提供的 REST API 或各种客户端库(如 Java 的 Elasticsearch High - Level REST Client)来实现数据的索引。
以 Java 的 Elasticsearch High - Level REST Client 为例,假设商品实体类为 Product
,索引名为 products
,以下是将商品数据索引到 Elasticsearch 的示例代码:
import org.apache.http.HttpHost;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
class Product {
private int id;
private String name;
private String description;
private double price;
private String category;
public Product(int id, String name, String description, double price, String category) {
this.id = id;
this.name = name;
this.description = description;
this.price = price;
this.category = category;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public double getPrice() {
return price;
}
public String getCategory() {
return category;
}
}
public class ElasticsearchIndexer {
private static final String INDEX_NAME = "products";
public static void main(String[] args) throws IOException {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
Product product = new Product(1, "iPhone 14", "A great smartphone", 999.99, "Electronics");
IndexRequest request = new IndexRequest(INDEX_NAME)
.id(String.valueOf(product.getId()))
.source("{\"name\":\"" + product.getName() + "\",\"description\":\"" + product.getDescription() + "\",\"price\":" + product.getPrice() + ",\"category\":\"" + product.getCategory() + "\"}", XContentType.JSON);
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
System.out.println(response.getResult());
client.close();
}
}
上述代码创建了一个 RestHighLevelClient
客户端,然后构建了一个 IndexRequest
请求,将商品数据以 JSON 格式存储到 Elasticsearch 的 products
索引中。
3.2.2 使用 Elasticsearch 进行搜索
Elasticsearch 提供了丰富的查询语法来满足不同的搜索需求。以下是使用 Java 代码实现不同搜索需求的示例:
使用 match 查询进行全文搜索
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;
public class ElasticsearchSearchMatch {
private static final String INDEX_NAME = "products";
public static void main(String[] args) throws IOException {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
// 创建 match 查询
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", "手机");
// 构建搜索请求
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchQuery);
SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
searchRequest.source(searchSourceBuilder);
// 执行搜索
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 处理搜索结果
System.out.println(searchResponse);
client.close();
}
}
这个查询会在 products
索引的 name
字段中搜索包含 “手机” 的文档。
使用 bool 查询进行多条件筛选
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;
public class ElasticsearchSearchBool {
private static final String INDEX_NAME = "products";
public static void main(String[] args) throws IOException {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
// 创建 bool 查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 添加 must 子查询
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("name", "手机");
TermQueryBuilder termQuery = QueryBuilders.termQuery("category", "电子产品");
boolQuery.must(matchQuery);
boolQuery.must(termQuery);
// 添加 filter 子查询
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price").gte(500).lte(1000);
boolQuery.filter(rangeQuery);
// 构建搜索请求
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(boolQuery);
SearchRequest searchRequest = new SearchRequest(INDEX_NAME);
searchRequest.source(searchSourceBuilder);
// 执行搜索
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
// 处理搜索结果
System.out.println(searchResponse);
client.close();
}
}
这个代码实现了搜索 “电子产品” 分类下价格在 500 到 1000 之间且名称包含 “手机” 的商品。
3.2.3 优势分析
Elasticsearch 相较于传统数据库搜索,在性能和功能上具有显著优势:
- 分布式架构:Elasticsearch 采用分布式架构,能够轻松应对大规模数据的存储和搜索需求。可以通过添加节点来扩展集群,提高搜索性能和可用性,适用于数据量不断增长的商城业务。
- 强大的查询功能:提供了丰富的查询语法和灵活的查询组合方式,能够实现复杂的搜索逻辑,如模糊搜索、多条件筛选、短语匹配、高亮显示等,满足用户多样化的搜索需求。
- 实时搜索:Elasticsearch 能够近乎实时地更新索引,保证搜索结果的及时性。当商品数据发生变化时,能够快速反映在搜索结果中,提升用户体验。