# 搜索商品
# 需求分析
- 标题模糊搜索
- 有分页查询
- 显示的商品信息有:首图、最低的规格价格、总销量、商品标题
- 有排序:默认、销量、价格
# SQL 分析
一个多表关联查询就可以了
select i.id as itemId,
i.item_name as itemName,
i.sell_counts as sellCounts,
ii.url as imgUrl
from items i
left join items_img ii on i.id = ii.item_id and ii.is_main = 1;
1
2
3
4
5
6
2
3
4
5
6
查询出了商品的基本信息
itemId | itemName | sellCounts | imgUrl |
---|---|---|---|
cake-1001 | 【天天吃货】真香预警 超级好吃 手撕面包 儿童早餐早饭 | 1003 | http://122.152.205.72:88/foodie/cake-1001/img1.png |
cake-1002 | 【天天吃货】网红烘焙蛋糕 好吃的蛋糕 | 363 | http://122.152.205.72:88/foodie/cake-1002/img1.png |
cake-1003 | 【天天吃货】超好吃华夫饼 美食诱惑 下午茶 | 636 | http://122.152.205.72:88/foodie/cake-1003/img1.png |
bingan-1001 | 【天天吃货】彩虹马卡龙 下午茶 美眉最爱 | 396 | http://122.152.205.72:88/foodie/bingan-1001/img1.png |
bingan-1002 | 【天天吃货】男人最爱 秋葵饼干 嘎嘣脆 | 424 | http://122.152.205.72:88/foodie/bingan-1002/img1.png |
bingan-1003 | 【天天吃货】可爱动物饼干 儿童早餐 孩子最爱 | 868 | http://122.152.205.72:88/foodie/bingan-1003/img1.png |
现在还差一个最低价格,在商品规格表中
select item_id, min(price_discount) as price_discount
from items_spec
group by item_id;
1
2
3
2
3
这样每个商品的最低价格就拿到了
item_id | price_discount |
---|---|
bingan-1001 | 9000 |
bingan-1002 | 10000 |
bingan-1003 | 8000 |
将两个 SQL 合并一下
select i.id as itemId,
i.item_name as itemName,
i.sell_counts as sellCounts,
ii.url as imgUrl,
tempSpec.price_discount price
from items i
left join items_img ii on i.id = ii.item_id and ii.is_main = 1
left join (select item_id, min(price_discount) as price_discount
from items_spec
group by item_id) tempSpec on tempSpec.item_id = ii.item_id
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
itemId | itemName | sellCounts | imgUrl | price |
---|---|---|---|---|
cake-1001 | 【天天吃货】真香预警 超级好吃 手撕面包 儿童早餐早饭 | 1003 | http://122.152.205.72:88/foodie/cake-1001/img1.png | 17600 |
cake-1002 | 【天天吃货】网红烘焙蛋糕 好吃的蛋糕 | 363 | http://122.152.205.72:88/foodie/cake-1002/img1.png | 32000 |
cake-1003 | 【天天吃货】超好吃华夫饼 美食诱惑 下午茶 | 636 | http://122.152.205.72:88/foodie/cake-1003/img1.png | 20000 |
# 商品搜索接口实现
<select id="searchItems" parameterType="Map" resultType="cn.mrcode.foodiedev.pojo.vo.SearchItemsVO">
select i.id as itemId,
i.item_name as itemName,
i.sell_counts as sellCounts,
ii.url as imgUrl,
tempSpec.price_discount price
from items i
left join items_img ii on i.id = ii.item_id and ii.is_main = 1
left join (select item_id, min(price_discount) as price_discount
from items_spec
group by item_id) tempSpec on tempSpec.item_id = ii.item_id
<where>
<if test="paramsMap.keywords != null and paramsMap.keywords != '' ">
i.item_name like '%${paramsMap.keywords}%'
</if>
</where>
order by
<choose>
<when test=" paramsMap.sort == "c"">
i.sell_counts desc
</when>
<when test=" paramsMap.sort == "p"">
tempSpec.price_discount asc
</when>
<otherwise>
i.item_name asc
</otherwise>
</choose>
</select>
<!-- k: 默认,根据 name-->
<!-- c: 根据效率排序-->
<!-- p: 根据价格排序-->
<!-- quot: 是双引号转义,使用单引号会被识别为数字类型,格式化报错,因为一个字母 char 可以识别为是数字-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
List<SearchItemsVO> searchItems(@Param("paramsMap") Map<String, Object> map);
1
响应类型
/**
* 用于展示商品搜索列表结果的 VO
*/
public class SearchItemsVO {
private String itemId;
private String itemName;
private int sellCounts;
private String imgUrl;
/**
* 价格是以分为单位,原因是因为:计算时有四舍五入,以分为单位的话,差别就不会特别大
*/
private int price;
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
service
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public PagedGridResult searchItems(String keywords, String sort, Integer page, Integer pageSize) {
Map<String, Object> map = new HashMap<>();
map.put("keywords", keywords);
map.put("sort", sort);
PageHelper.startPage(page, pageSize);
List<SearchItemsVO> list = itemsMapperCustom.searchItems(map);
return setterPagedGrid(list, page);
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
controller
@ApiOperation(value = "搜索商品列表", notes = "搜索商品列表", httpMethod = "GET")
@GetMapping("/search")
public JSONResult search(
@ApiParam(name = "keywords", value = "关键词", required = true)
@RequestParam String keywords,
@ApiParam(name = "sort", value = "排序, " +
" -- k: 默认,根据 name\n" +
" -- c: 根据效率排序\n" +
" -- p: 根据价格排序", required = false)
@RequestParam String sort,
@ApiParam(name = "page", value = "查询下一页的第几页", required = false)
@RequestParam Integer page,
@ApiParam(name = "pageSize", value = "分页的每一页显示的条数", required = false)
@RequestParam Integer pageSize) {
if (page == null) {
page = 1;
}
if (pageSize == null) {
// 继承了 BaseController 中定义的评论默认分页数量
pageSize = COMMON_PAGE_SIZE;
}
PagedGridResult grid = itemService.searchItems(keywords,
sort,
page,
pageSize);
return JSONResult.ok(grid);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 按商品分类搜索
从首页分类跳转过来的话,是需要按分类搜索的,在跳转路径 http://localhost:8080/foodie-shop/catItems.html?searchType=catItems&catId=37
里面包含了两个参数:
- searchType:搜索类型,catItems 则是按分类搜索,searchItems 则是按标题搜索
- catId:分类 ID
在这里看,分类 ID 其实就在商品表中,我们完全可以使用上面的商品搜索接口来实现,但是你作为架构师,暴露的接口给前端使用,你就要考虑到后续的接口调用场景,因为这两个入口的场景不一样:
- 一个是在首页分类进入
- 一个是在专门的商品搜索入口
这两个场景可能在后续的业务发展中,由于业务场景的不同,他们的要求也不同,合并在一起势必会造成你的后端代码越来越复杂。这一点你必须明确
所以在这里,我们会分开接口提供,后端服务的查询代码也是分开提供;
需要注意的是:这里的业务设置是:从首页跳转过来的分类搜索:不支持模糊搜索,搜索框里面触发的搜索会走文本搜索,现在这个前端是这样实现的。
下面的代码只是针对前面的代码进行了一点点的修改
List<SearchItemsVO> searchItemsByThirdCat(@Param("paramsMap") Map<String, Object> map);
1
<select id="searchItemsByThirdCat" parameterType="Map" resultType="cn.mrcode.foodiedev.pojo.vo.SearchItemsVO">
select i.id as itemId,
i.item_name as itemName,
i.sell_counts as sellCounts,
ii.url as imgUrl,
tempSpec.price_discount price
from items i
left join items_img ii on i.id = ii.item_id and ii.is_main = 1
left join (select item_id, min(price_discount) as price_discount
from items_spec
group by item_id) tempSpec on tempSpec.item_id = ii.item_id
<where>
i.cat_id = #{catId}
</where>
order by
<choose>
<when test=" paramsMap.sort == "c"">
i.sell_counts desc
</when>
<when test=" paramsMap.sort == "p"">
tempSpec.price_discount asc
</when>
<otherwise>
i.item_name asc
</otherwise>
</choose>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public PagedGridResult searchItemsByThirdCat(Integer catId, String sort, Integer page, Integer pageSize) {
Map<String, Object> map = new HashMap<>();
map.put("catId", catId);
map.put("sort", sort);
PageHelper.startPage(page, pageSize);
List<SearchItemsVO> list = itemsMapperCustom.searchItemsByThirdCat(map);
return setterPagedGrid(list, page);
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
@ApiOperation(value = "搜索商品列表-按分类ID", notes = "按分类 ID 搜素商品列表", httpMethod = "GET")
@GetMapping("/catItems")
public JSONResult catItems(
@ApiParam(name = "catId", value = "三级分类 ID", required = true)
@RequestParam Integer catId,
@ApiParam(name = "sort", value = "排序, " +
" -- k: 默认,根据 name\n" +
" -- c: 根据效率排序\n" +
" -- p: 根据价格排序", required = false)
@RequestParam String sort,
@ApiParam(name = "page", value = "查询下一页的第几页", required = false)
@RequestParam Integer page,
@ApiParam(name = "pageSize", value = "分页的每一页显示的条数", required = false)
@RequestParam Integer pageSize) {
if (page == null) {
page = 1;
}
if (pageSize == null) {
pageSize = COMMON_PAGE_SIZE;
}
PagedGridResult grid = itemService.searchItemsByThirdCat(catId,
sort,
page,
pageSize);
return JSONResult.ok(grid);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29