樂優商城--商品規格(資料庫分析、商品查詢)
-
瞭解商品規格資料結構設計思路
-
實現商品規格查詢
-
瞭解SPU和SKU資料結構設計思路
-
實現商品查詢
-
瞭解商品新增的頁面實現
-
獨立編寫商品新增後臺功能
1.商品規格資料結構
樂優商城是一個全品類的電商網站,因此商品的種類繁多,每一件商品,其屬性又有差別。為了更準確描述商品及細分差別,抽象出兩個概念:SPU和SKU,瞭解一下:
1.1.SPU和SKU
SPU:Standard Product Unit (標準產品單位) ,一組具有共同屬性的商品集
SKU:Stock Keeping Unit(庫存量單位),SPU商品集因具體特性不同而細分的每個商品
以圖為例來看:
-
本頁的 華為Mate10 就是一個商品集(SPU)
-
因為顏色、記憶體等不同,而細分出不同的Mate10,如亮黑色128G版。(SKU)
可以看出:
-
SPU是一個抽象的商品集概念,為了方便後臺的管理。
-
SKU才是具體要銷售的商品,每一個SKU的價格、庫存可能會不一樣,使用者購買的是SKU而不是SPU
1.2.資料庫設計分析
1.2.1.思考並發現問題
弄清楚了SPU和SKU的概念區分,接下來我們一起思考一下該如何設計資料庫表。
首先來看SPU,大家一起思考下SPU應該有哪些欄位來描述?
id:主鍵 title:標題 description:描述 specification:規格 packaging_list:包裝 after_service:售後服務 comment:評價 category_id:商品分類 brand_id:品牌
似乎並不複雜,但是大家仔細思考一下,商品的規格欄位你如何填寫?
不同商品的規格不一定相同,資料庫中要如何儲存?
再看下SKU,大家覺得應該有什麼欄位?
id:主鍵
spu_id:關聯的spu
price:價格
images:圖片
stock:庫存
顏色?
記憶體?
硬碟?
碰到難題了,不同的商品分類,可能屬性是不一樣的,比如手機有記憶體,衣服有尺碼,我們是全品類的電商網站,這些不同的商品的不同屬性,如何設計到一張表中?
其實顏色、記憶體、硬碟屬性都是規格引數中的欄位。所以,要解決這個問題,首先要能清楚規格引數。
1.2.2.分析規格引數
仔細檢視每一種商品的規格你會發現:
雖然商品規格千變萬化,但是同一類商品(如手機)的規格是統一的,有圖為證:
華為的規格:
三星的規格:
1.2.3.SKU的特有屬性
SPU中會有一些特殊屬性,用來區分不同的SKU,我們稱為SKU特有屬性。如華為META10的顏色、記憶體屬性。
不同種類的商品,一個手機,一個衣服,其SKU屬性不相同。
同一種類的商品,比如都是衣服,SKU屬性基本是一樣的,都是顏色、尺碼等。
這樣說起來,似乎SKU的特有屬性也是與分類相關的?事實上,仔細觀察你會發現,SKU的特有屬性是商品規格引數的一部分:
也就是說,我們沒必要單獨對SKU的特有屬性進行設計,它可以看做是規格引數中的一部分。這樣規格引數中的屬性可以標記成兩部分:
-
spu下所有sku共享的規格屬性(稱為全域性屬性)
-
每個sku不同的規格屬性(稱為特有屬性)
1.2.4.搜尋屬性
開啟一個搜尋頁,我們來看看過濾的條件:
你會發現,過濾條件中的螢幕尺寸、執行記憶體、網路、機身記憶體、電池容量、CPU核數等,在規格引數中都能找到:
也就是說,規格引數中的資料,將來會有一部分作為搜尋條件來使用。我們可以在設計時,將這部分屬性標記出來,將來做搜尋的時候,作為過濾條件。要注意的是,無論是SPU的全域性屬性,還是SKU的特有屬性,都有可能作為搜尋過濾條件的,並不衝突,而是有一個交集:
1.3.規格引數表
1.3.1.表結構
我們看下規格引數的格式:
可以看到規格引數是分組的,每一組都有多個引數鍵值對。不過對於規格引數的模板而言,其值現在是不確定的,不同的商品值肯定不同,模板中只要儲存組資訊、組內參數資訊即可。
因此我們設計了兩張表:
-
tb_spec_group:組,與商品分類關聯
-
tb_spec_param:引數名,與組關聯,一對多
1.3.2.規格組
規格引數分組表:tb_spec_group
CREATE TABLE `tb_spec_group` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵', `cid` bigint(20) NOT NULL COMMENT '商品分類id,一個分類下有多個規格組', `name` varchar(50) NOT NULL COMMENT '規格組的名稱', PRIMARY KEY (`id`), KEY `key_category` (`cid`) ) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='規格引數的分組表,每個商品分類下有多個規格引數組';
規格組有3個欄位:
-
id:主鍵
-
cid:商品分類id,一個分類下有多個模板
-
name:該規格組的名稱。
1.3.2.規格引數
規格引數表:tb_spec_param
CREATE TABLE `tb_spec_param` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵', `cid` bigint(20) NOT NULL COMMENT '商品分類id', `group_id` bigint(20) NOT NULL, `name` varchar(255) NOT NULL COMMENT '引數名', `numeric` tinyint(1) NOT NULL COMMENT '是否是數字型別引數,true或false', `unit` varchar(255) DEFAULT '' COMMENT '數字型別引數的單位,非數字型別可以為空', `generic` tinyint(1) NOT NULL COMMENT '是否是sku通用屬性,true或false', `searching` tinyint(1) NOT NULL COMMENT '是否用於搜尋過濾,true或false', `segments` varchar(1000) DEFAULT '' COMMENT '數值型別引數,如果需要搜尋,則新增分段間隔值,如CPU頻率間隔:0.5-1.0', PRIMARY KEY (`id`), KEY `key_group` (`group_id`), KEY `key_category` (`cid`) ) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8 COMMENT='規格引數組下的引數名';
按道理來說,我們的規格引數就只需要記錄引數名、組id、商品分類id即可。但是這裡卻多出了很多欄位,為什麼?
還記得我們之前的分析吧,規格引數中有一部分是 SKU的通用屬性,一部分是SKU的特有屬性,而且其中會有一些將來用作搜尋過濾,這些資訊都需要標記出來。
通用屬性
用一個布林型別欄位來標記是否為通用:
-
generic來標記是否為通用屬性:
-
true:代表通用屬性
-
false:代表sku特有屬性
-
搜尋過濾
與搜尋相關的有兩個欄位:
-
searching:標記是否用作過濾
-
true:用於過濾搜尋
-
false:不用於過濾
-
-
segments:某些數值型別的引數,在搜尋時需要按區間劃分,這裡提前確定好劃分區間
-
比如電池容量,0~2000mAh,2000mAh~3000mAh,3000mAh~4000mAh
-
數值型別
某些規格引數可能為數值型別,這樣的資料才需要劃分區間,我們有兩個欄位來描述:
-
numberic:是否為數值型別
-
true:數值型別
-
false:不是數值型別
-
-
unit:引數的單位
2.商品規格引數管理
2.1.頁面佈局
2.1.1.整體佈局
開啟規格引數頁面,看到如下內容:
商品分類樹我們之前已經做過,所以這裡可以直接展示出來。
因為規格是跟商品分類繫結的,因此首先會展現商品分類樹,並且提示你要選擇商品分類,才能看到規格引數的模板。一起了解下頁面的實現:
頁面結構:
這裡使用了v-layout
來完成頁面佈局,並且添加了row屬性,代表接下來的內容是行佈局(左右)。
可以看出頁面分成2個部分:
-
<v-flex xs3>
:左側,內部又分上下兩部分:商品分類樹及標題-
v-card-title
:標題部分,這裡是提示資訊,告訴使用者要先選擇分類,才能看到模板 -
v-tree
:這裡用到的是我們之前講過的樹元件,展示商品分類樹,
-
-
<v-flex xs9 class="px-1">
:右側:內部是規格引數展示
2.1.2.右側規格
當我們點選一個分類時,最終要達到的效果:
可以看到右側分為上下兩部分:
-
上部:麵包屑,顯示當前選中的分類
-
下部:table,顯示規格引數資訊
頁面實現:
可以看到右側並不是我們熟悉的 v-data-table
,而是一個spec-group
元件(規格組)和spec-param
元件(規格引數),這是我們定義的獨立元件:
在SpecGroup中定義了表格:
2.2.規格組的查詢
2.2.1.樹節點的點選事件
當我們點選樹節點時,要將v-dialog
開啟,因此必須繫結一個點選事件:(Specification.vue)
我們來看下handleClick
方法:(Specification.vue)
點選事件發生時,發生了兩件事:
-
記錄當前選中的節點,選中的就是商品分類
-
showGroup
被置為true,則規格組就會顯示了。
同時,我們把被選中的節點(商品分類)的id傳遞給了SpecGroup
元件:(Specification.vue)
2.2.2.頁面查詢規格組
來看下SpecGroup.vue
中的實現:
我們檢視頁面控制檯,可以看到請求已經發出:
2.2.3.後端程式碼
實體類
在leyou-item-interface
中新增實體類:
內容:
@Table(name = "tb_spec_group") public class SpecGroup { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long cid; private String name; @Transient private List<SpecParam> params; // getter和setter省略 }
@Table(name = "tb_spec_param") public class SpecParam { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long cid; private Long groupId; private String name; @Column(name = "`numeric`") private Boolean numeric; private String unit; private Boolean generic; private Boolean searching; private String segments; // getter和setter ... }
在leyou-item-service
中編寫業務:
mapper
public interface SpecGroupMapper extends Mapper<SpecGroup> { }
controller
先分析下需要的東西,在頁面的ajax請求中可以看出:
-
請求方式:get
-
請求路徑:/spec/groups/{cid} ,這裡通過路徑佔位符傳遞商品分類的id
-
請求引數:商品分類id
-
返回結果:頁面是直接把
resp.data
賦值給了groups:那麼我們返回的應該是規格組SpecGroup的集合
程式碼:
@RestController @RequestMapping("spec") public class SpecificationController { @Autowired private SpecificationService specificationService; /** * 根據分類id查詢分組 * @param cid * @return */ @GetMapping("groups/{cid}") public ResponseEntity<List<SpecGroup>> queryGroupsByCid(@PathVariable("cid")Long cid){ List<SpecGroup> groups = this.specificationService.queryGroupsByCid(cid); if (CollectionUtils.isEmpty(groups)){ return ResponseEntity.notFound().build(); } return ResponseEntity.ok(groups); } }
service
@Service public class SpecificationService { @Autowired private SpecGroupMapper groupMapper; /** * 根據分類id查詢分組 * @param cid * @return */ public List<SpecGroup> queryGroupsByCid(Long cid) { SpecGroup specGroup = new SpecGroup(); specGroup.setCid(cid); return this.groupMapper.select(specGroup); } }
頁面訪問測試:
目前,我們資料庫只為手機分類(76)提供了規格組:
我們訪問:http://api.leyou.com/api/item/spec/groups/76
然後在後臺系統中測試:
2.3.規格引數查詢
2.3.1.表格切換
當我們點選規格組,會切換到規格引數顯示,肯定是在規格組中綁定了點選事件:
我們看下事件處理:
可以看到這裡是使用了父子通訊,子元件觸發了select事件:
再來看下父元件的事件繫結:
事件處理:
這裡我們記錄了選中的分組,並且把標記設定為false,這樣規格組就不顯示了,而是顯示:SpecParam
並且,我們把group也傳遞到spec-param
元件:
2.3.2.頁面查詢規格引數
我們來看SpecParam.vue
的實現:
檢視頁面控制檯,發現請求已經發出:
報404,因為我們還沒有實現後臺邏輯,接下來就去實現。
2.3.3.後臺實現
SpecificationController
分析:
-
請求方式:GET
-
請求路徑:/spec/params
-
請求引數:gid,分組id
-
返回結果:該分組下的規格引數集合
List<SpecParam>
程式碼:
/** * 根據條件查詢規格引數 * @param gid * @return */ @GetMapping("params") public ResponseEntity<List<SpecParam>> queryParams(@RequestParam("gid")Long gid){ List<SpecParam> params = this.specificationService.queryParams(gid); if (CollectionUtils.isEmpty(params)){ return ResponseEntity.notFound().build(); } return ResponseEntity.ok(params); }
SpecificationService
@Autowired private SpecParamMapper paramMapper; /** * 根據條件查詢規格引數 * @param gid * @return */ public List<SpecParam> queryParams(Long gid) { SpecParam param = new SpecParam(); param.setGroupId(gid); return this.paramMapper.select(param); }
SpecParamMapper
public interface SpecParamMapper extends Mapper<SpecParam> { }
測試:
2.4.增、刪、改(作業)
增刪改的作業就留給大家去完成了。頁面中介面都已定義,你要做的就是實現後臺介面。
3.SPU和SKU資料結構
規格確定以後,就可以新增商品了,先看下資料庫表
3.1.SPU表
SPU表:
CREATE TABLE `tb_spu` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'spu id', `title` varchar(255) NOT NULL DEFAULT '' COMMENT '標題', `sub_title` varchar(255) DEFAULT '' COMMENT '子標題', `cid1` bigint(20) NOT NULL COMMENT '1級類目id', `cid2` bigint(20) NOT NULL COMMENT '2級類目id', `cid3` bigint(20) NOT NULL COMMENT '3級類目id', `brand_id` bigint(20) NOT NULL COMMENT '商品所屬品牌id', `saleable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否上架,0下架,1上架', `valid` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效,0已刪除,1有效', `create_time` datetime DEFAULT NULL COMMENT '新增時間', `last_update_time` datetime DEFAULT NULL COMMENT '最後修改時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=208 DEFAULT CHARSET=utf8 COMMENT='spu表,該表描述的是一個抽象的商品,比如 iphone8';
與我們前面分析的基本類似,但是似乎少了一些欄位,比如商品描述。
我們做了表的垂直拆分,將SPU的詳情放到了另一張表:tb_spu_detail
CREATE TABLE `tb_spu_detail` ( `spu_id` bigint(20) NOT NULL, `description` text COMMENT '商品描述資訊', `generic_spec` varchar(10000) NOT NULL DEFAULT '' COMMENT '通用規格引數資料', `special_spec` varchar(1000) NOT NULL COMMENT '特有規格引數及可選值資訊,json格式', `packing_list` varchar(3000) DEFAULT '' COMMENT '包裝清單', `after_service` varchar(3000) DEFAULT '' COMMENT '售後服務', PRIMARY KEY (`spu_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
這張表中的資料都比較大,為了不影響主表的查詢效率我們拆分出這張表。
需要注意的是這兩個欄位:generic_spec和special_spec。
前面講過規格引數與商品分類繫結,一個分類下的所有SPU具有類似的規格引數。SPU下的SKU可能會有不同的規格引數資訊,因此我們計劃是這樣:
-
SPUDetail中儲存通用的規格引數資訊。
-
SKU中儲存特有規格引數。
來看下我們的表如何儲存這些資訊。
3.1.1.generic_spec欄位
首先是generic_spec
,其中儲存通用規格引數資訊的值,這裡為了方便查詢,使用了json格式:
整體來看:
json結構,其中都是鍵值對:
-
key:對應的規格引數的
spec_param
的id -
value:對應規格引數的值
3.1.2.special_spec欄位
我們說spu中只儲存通用規格引數,那麼為什麼有多出了一個special_spec
欄位呢?
以手機為例,品牌、作業系統等肯定是全域性通用屬性,記憶體、顏色等肯定是特有屬性。
當你確定了一個SPU,比如小米的:紅米4X
全域性屬性值都是固定的了:
品牌:小米
型號:紅米4X
特有屬性舉例:
顏色:[香檳金, 櫻花粉, 磨砂黑] 記憶體:[2G, 3G] 機身儲存:[16GB, 32GB]
顏色、記憶體、機身儲存,作為SKU特有屬性,key雖然一樣,但是SPU下的每一個SKU,其值都不一樣,所以值會有很多,形成陣列。
我們在SPU中,會把特有屬性的所有值都記錄下來,形成一個數組:
裡面又有哪些內容呢?
來看資料格式:
也是json結構:
-
key:規格引數id
-
value:spu屬性的陣列
那麼問題來:特有規格引數應該在sku中記錄才對,為什麼在spu中也要記錄一份?
因為我們有時候需要把所有規格引數都查詢出來,而不是隻查詢1個sku的屬性。比如,商品詳情頁展示可選的規格引數時:
剛好符合我們的結構,這樣頁面渲染就非常方便了。
3.2.SKU表
CREATE TABLE `tb_sku` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'sku id', `spu_id` bigint(20) NOT NULL COMMENT 'spu id', `title` varchar(255) NOT NULL COMMENT '商品標題', `images` varchar(1000) DEFAULT '' COMMENT '商品的圖片,多個圖片以‘,’分割', `price` bigint(15) NOT NULL DEFAULT '0' COMMENT '銷售價格,單位為分', `indexes` varchar(100) COMMENT '特有規格屬性在spu屬性模板中的對應下標組合', `own_spec` varchar(1000) COMMENT 'sku的特有規格引數,json格式', `enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效,0無效,1有效', `create_time` datetime NOT NULL COMMENT '新增時間', `last_update_time` datetime NOT NULL COMMENT '最後修改時間', PRIMARY KEY (`id`), KEY `key_spu_id` (`spu_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='sku表,該表表示具體的商品實體,如黑色的64GB的iphone 8';
還有一張表,代表庫存:
CREATE TABLE `tb_stock` ( `sku_id` bigint(20) NOT NULL COMMENT '庫存對應的商品sku id', `seckill_stock` int(9) DEFAULT '0' COMMENT '可秒殺庫存', `seckill_total` int(9) DEFAULT '0' COMMENT '秒殺總數量', `stock` int(9) NOT NULL COMMENT '庫存數量', PRIMARY KEY (`sku_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='庫存表,代表庫存,秒殺庫存等資訊';
問題:為什麼要將庫存獨立一張表?
因為庫存欄位寫頻率較高,而SKU的其它欄位以讀為主,因此我們將兩張表分離,讀寫不會干擾。
特別需要注意的是sku表中的indexes
欄位和own_spec
欄位。sku中應該儲存特有規格引數的值,就在這兩個欄位中。
3.2.1.indexes欄位
在SPU表中,已經對特有規格引數及可選項進行了儲存,結構如下:
{ "4": [ "香檳金", "櫻花粉", "磨砂黑" ], "12": [ "2GB", "3GB" ], "13": [ "16GB", "32GB" ] }
這些特有屬性如果排列組合,會產生12個不同的SKU,而不同的SKU,其屬性就是上面備選項中的一個。
比如:
-
紅米4X,香檳金,2GB記憶體,16GB儲存
-
紅米4X,磨砂黑,2GB記憶體,32GB儲存
你會發現,每一個屬性值,對應於SPUoptions陣列的一個選項,如果我們記錄下角標,就是這樣:
-
紅米4X,0,0,0
-
紅米4X,2,0,1
既然如此,我們是不是可以將不同角標串聯起來,作為SPU下不同SKU的標示。這就是我們的indexes欄位。
這個設計在商品詳情頁會特別有用:
當用戶點選選中一個特有屬性,你就能根據 角標快速定位到sku。
3.2.2.own_spec欄位
看結構:
{"4":"香檳金","12":"2GB","13":"16GB"}
儲存的是特有屬性的鍵值對。
SPU中儲存的是可選項,但不確定具體的值,而SKU中的儲存的就是具體的值。
3.3.匯入圖片資訊
現在商品表中雖然有資料,但是所有的圖片資訊都是無法訪問的,我們需要把圖片匯入到虛擬機器:
首先,把課前資料提供的資料上傳到虛擬機器下:/leyou/static
目錄:在leyou下建立static目錄
然後,使用命令解壓縮:
unzip images.zip
修改Nginx配置,使nginx反向代理這些圖片地址:
vim /opt/nginx/config/nginx.conf
修改成如下配置:
server { listen 80; server_name image.leyou.com; # 監聽域名中帶有group的,交給FastDFS模組處理 location ~/group([0-9])/ { ngx_fastdfs_module; } # 將其它圖片代理指向本地的/leyou/static目錄 location / { root /leyou/static/; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }
不要忘記重新載入nginx配置
nginx -s reload
4.商品查詢
4.1.效果預覽
接下來,我們實現商品管理的頁面,先看下我們要實現的效果:
可以看出整體是一個table,然後有新增按鈕。是不是跟昨天寫品牌管理很像?
4.2.頁面請求
先看整體頁面結構(Goods.vue):
並且在Vue例項掛載後就會發起查詢(mounted呼叫getDataFromServer方法初始化資料):
我們重新整理頁面,可以看到瀏覽器發起已經發起了查詢商品資料的請求:
因此接下來,我們編寫介面即可。
4.3.後臺提供介面
頁面已經準備好,接下來在後臺提供分頁查詢SPU的功能。
4.3.1.實體類
在leyou-item-interface工程中新增實體類:
SPU
@Table(name = "tb_spu") public class Spu { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long brandId; private Long cid1;// 1級類目 private Long cid2;// 2級類目 private Long cid3;// 3級類目 private String title;// 標題 private String subTitle;// 子標題 private Boolean saleable;// 是否上架 private Boolean valid;// 是否有效,邏輯刪除用 private Date createTime;// 建立時間 private Date lastUpdateTime;// 最後修改時間 // 省略getter和setter }
SPU詳情
@Table(name="tb_spu_detail") public class SpuDetail { @Id private Long spuId;// 對應的SPU的id private String description;// 商品描述 private String specialSpec;// 商品特殊規格的名稱及可選值模板 private String genericSpec;// 商品的全域性規格屬性 private String packingList;// 包裝清單 private String afterService;// 售後服務 // 省略getter和setter }
4.4.2.mapper
public interface SpuMapper extends Mapper<Spu> { }
4.3.3.controller
先分析:
-
請求方式:GET
-
請求路徑:/spu/page
-
請求引數:
-
page:當前頁
-
rows:每頁大小
-
key:過濾條件
-
saleable:上架或下架
-
-
返回結果:商品SPU的分頁資訊。
-
要注意,頁面展示的是商品分類和品牌名稱,而資料庫中儲存的是id,怎麼辦?
我們可以新建一個類,繼承SPU,並且拓展cname和bname屬性,寫到
leyou-item-interface
public class SpuBo extends Spu { String cname;// 商品分類名稱 String bname;// 品牌名稱 // 略 。。 }
-
編寫controller程式碼:
我們把與商品相關的一切業務介面都放到一起,起名為GoodsController,業務層也是這樣
@Controller public class GoodsController { @Autowired private GoodsService goodsService; @GetMapping("spu/page") public ResponseEntity<PageResult<SpuBo>> querySpuBoByPage( @RequestParam(value = "key", required = false)String key, @RequestParam(value = "saleable", required = false)Boolean saleable, @RequestParam(value = "page", defaultValue = "1")Integer page, @RequestParam(value = "rows", defaultValue = "5")Integer rows ){ PageResult<SpuBo> pageResult = this.goodsService.querySpuBoByPage(key, saleable, page, rows); if(CollectionUtils.isEmpty(pageResult.getItems())){ return ResponseEntity.notFound().build(); } return ResponseEntity.ok(pageResult); } }
4.4.4.service
所有商品相關的業務(包括SPU和SKU)放到一個業務下:GoodsService。
@Service public class GoodsService { @Autowired private SpuMapper spuMapper; @Autowired private CategoryService categoryService; @Autowired private BrandMapper brandMapper; public PageResult<SpuBo> querySpuBoByPage(String key, Boolean saleable, Integer page, Integer rows) { Example example = new Example(Spu.class); Example.Criteria criteria = example.createCriteria(); // 搜尋條件 if (StringUtils.isNotBlank(key)) { criteria.andLike("title", "%" + key + "%"); } if (saleable != null) { criteria.andEqualTo("saleable", saleable); } // 分頁條件 PageHelper.startPage(page, rows); // 執行查詢 List<Spu> spus = this.spuMapper.selectByExample(example); PageInfo<Spu> pageInfo = new PageInfo<>(spus); List<SpuBo> spuBos = new ArrayList<>(); spus.forEach(spu->{ SpuBo spuBo = new SpuBo(); // copy共同屬性的值到新的物件 BeanUtils.copyProperties(spu, spuBo); // 查詢分類名稱 List<String> names = this.categoryService.queryNamesByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3())); spuBo.setCname(StringUtils.join(names, "/")); // 查詢品牌的名稱 spuBo.setBname(this.brandMapper.selectByPrimaryKey(spu.getBrandId()).getName()); spuBos.add(spuBo); }); return new PageResult<>(pageInfo.getTotal(), spuBos); } }
4.4.5.Category中拓展查詢名稱的功能
頁面需要商品的分類名稱需要在這裡查詢,因此要額外提供查詢分類名稱的功能,
在CategoryService中新增功能:
public List<String> queryNamesByIds(List<Long> ids) { List<Category> list = this.categoryMapper.selectByIdList(ids); List<String> names = new ArrayList<>(); for (Category category : list) { names.add(category.getName()); } return names; // return list.stream().map(category -> category.getName()).collect(Collectors.toList()); }
mapper的selectByIdList方法是來自於通用mapper。不過需要我們在mapper上繼承一個通用mapper介面:
public interface CategoryMapper extends Mapper<Category>, SelectByIdListMapper<Category, Long> { }
4.5.測試
重新整理頁面,檢視效果:
基本與預覽的效果一致,OK!