樂優商城(七)商品規格管理
目錄
商品規格管理
一、商品規格資料結構
樂優商城是一個全品類的電商網站,因此商品的種類繁多,每一件商品,其屬性又有差別。為了更準確描述商品及細分差別,抽象出兩個概念:SPU和SKU。
1.1 SPU和SKU
SPU:Standard Product Unit (標準產品單位) ,一組具有共同屬性的商品集
SKU:Stock Keeping Unit(庫存量單位),SPU商品集因具體特性不同而細分的每個商品
- 本頁的 vivo x21就是一個商品集(SPU)
- 因為顏色、記憶體等不同,而細分出不同的x21,如亮冰鑽黑6+128G版。(SKU)
可以看出:
- SPU是一個抽象的商品集概念,為了方便後臺的管理。
- SKU才是具體要銷售的商品,每一個SKU的價格、庫存可能會不一樣,使用者購買的是SKU而不是SPU
1.2 資料庫設計分析
1.2.1 規格引數分析
當仔細檢視每一種規格後就會發現,雖然商品規格千變萬化,但是同一類商品(手機)的規格是統一的,如下圖所示:
vivo的規格
oppo的規格
所以,商品的規格引數應該是與分類繫結的,每個分類都有統一的規格引數模板,dan'shi 不同商品的引數值可能不同。
如下圖所示:
1.2.2 SKU特有屬性
SPU中會有一些特殊屬性,用來區分不同的SKU,我們稱為SKU特有屬性。如vivo x21的顏色、記憶體屬性。不同種類的商品,一個手機,一個衣服,其SKU屬性不相同。同一種類的商品,比如都是衣服,SKU屬性基本是一樣的,都是顏色、尺碼等。
這樣說起來,似乎SKU的特有屬性也是與分類相關的?事實上,仔細觀察會發現,SKU的特有屬性是商品規格引數的一部分:
也就是說,沒必要單獨對SKU的特有屬性進行設計,它可以看做是規格引數中的一部分。這樣規格引數中的屬性可以標記成兩部分:
- 所有sku共享的規格屬性(稱為全域性屬性)
- 每個sku不同的規格屬性(稱為特有屬性)
1.2.3 搜尋屬性
開啟搜尋頁,檢視過濾條件:
過濾條件中的螢幕尺寸、執行記憶體、機身記憶體、CPU核數等,在規格引數中都能找到:
也就是說,規格引數中的資料,將來會有一部分作為搜尋條件來使用。所以在設計時,將這部分屬性標記出來,將來做搜尋的時候,作為過濾條件。要注意的是,無論是SPU的全域性屬性,還是SKU的特有屬性,都有可能作為搜尋過濾條件的,並不衝突,而是有一個交集:
1.3 規格引數表
1.3.1 表結構
CREATE TABLE `tb_specification` (
`category_id` bigint(20) NOT NULL COMMENT '規格模板所屬商品分類id',
`specifications` varchar(3000) NOT NULL DEFAULT '' COMMENT '規格引數模板,json格式',
PRIMARY KEY (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品規格引數模板,json格式。';
specificatons:規格引數模板,json格式
如果按照傳統資料庫設計,這裡至少需要3張表:
-
group:代表組,與商品分類關聯
-
param_key:屬性名,與組關聯,一對多
-
param_value:屬性備選值,與屬性名關聯,一對多
這樣程式的複雜度大大增加,但是提高了資料的複用性。
所以解決方案是,採用json來儲存整個規格引數模板,不需要額外的表,一個字串就夠了。
1.3.2 JSON結構分析
整體
- 因為規格引數分為很多組,所以json最外層是一個數組。
- 陣列中是物件型別,每個物件代表一個組的資料,物件的屬性包括:
group:組的名稱
params:該組的所有屬性
params
以主晶片
這一組為例:
-
group:註明,這裡是主晶片
-
params:該組的所有規格屬性,因為不止一個,所以是一個數組。這裡包含四個規格屬性:CPU品牌,CPU型號,CPU頻率,CPU核數。每個規格屬性都是一個物件,包含以下資訊:
-
k:屬性名稱
-
searchable:是否作為搜尋欄位,將來在搜尋頁面使用,boolean型別
-
global:是否是SPU全域性屬性,boolean型別。true為全域性屬性,false為SKU的特有屬性
-
options:屬性值的可選項,陣列結構。起約束作用,不允許填寫可選項以外的值,比如CPU核數,有人添10000核豈不是很扯淡
-
numerical:是否為數值,boolean型別,true則為數值,false則不是。為空也代表非數值
-
unit:單位,如:克,毫米。如果是數值型別,那麼就需要有單位,否則可以不填。
-
以上屬性都是全域性屬性,下面是特殊屬性:
總結下:
-
規格引數分組,每組有多個引數
-
引數的
k
代表屬性名稱,沒有值,具體的SPU才能確定值 -
引數會有不同的屬性:是否可搜尋,是否是全域性、是否是數值,這些都用boolean值進行標記:
-
SPU下的多個SKU共享的引數稱為全域性屬性,用
global=true
標記 -
SPU下的多個SKU特有的引數稱為特有屬性,用
global=false
標記 -
如果引數是數值型別,用
numerical
標記,並且指定單位unit
-
如果引數可搜尋,用
searchable
標記
-
二、商品規格引數管理
2.1 頁面分析
因為規格是跟商品分類繫結的,因此首先會展現商品分類樹,並且提示你要選擇商品分類,才能看到規格引數的模板。
可以看出頁面分成3個部分:
-
v-card-title
:標題部分,這裡是提示資訊,告訴使用者要先選擇分類,才能看到模板 -
v-tree
:這裡用到的是我們之前講過的樹元件,展示商品分類樹,不過現在是假資料,我們只要把treeData
屬性刪除,它就會走url
屬性指定的路徑去查詢真實的商品分類樹了。<v-tree url="/item/category/list" :isEdit="false" @handleClick="handleClick" />
-
v-dialog
:Vuetify提供的對話方塊元件,v-model繫結的dialog屬性是boolean型別:-
true則顯示彈窗
-
false則隱藏彈窗
-
再看一下Vue例項中data定義了哪些屬性,對頁面會產生怎樣的影響:
-
specifications:選中一個商品分類後,需要查詢後臺獲取規格引數資訊,儲存在這個物件中,Vue會完成頁面渲染。
-
oldSpec:當前頁兼具了規格的增、改、查等功能,這個物件記錄被修改前的規格引數,以防使用者撤銷修改,用來恢復資料。
-
dialog:是否顯示對話方塊的標記。true則顯示,false則不顯示
-
currentNode:記錄當前選中的商品分類節點
-
units:數值型別的可選單位
2.2 規格引數查詢
點選樹葉子節點後要顯示規格引數,因此查詢功能應該編寫在點選事件中。
2.2.1 樹節點的點選事件
handleClick(node) {
// 把當前點選IDE節點記錄下來
this.currentNode = node;
// 判斷點選的節點是否是父節點(只有點選到葉子節點才會彈窗)
if (!node.isParent) {
// 如果是葉子節點,那麼就發起ajax請求,去後臺查詢商品規格資料。
this.$http.get("/item/spec/" + node.id)
.then(resp => {
console.log(resp.data)
// 查詢成功後,把響應結果賦值給specifications屬性,Vue會進行自動渲染。
this.specifications = resp.data;
// 記錄下此時的規格資料,當頁面撤銷修改時,用來恢復原始資料
this.oldSpec = resp.data;
// 開啟彈窗
this.dialog = true;
// 標記此時要進行修改操作
this.isInsert = false;
})
.catch(() => {
// 如果沒有查詢成功,那麼詢問是否新增規格
this.$message.confirm('該分類還沒有規格引數,是否新增?')
.then(() => {
// 如果要新增,則將specifications初始化為空
this.specifications = [{
group: '',
params: []
}];
// 開啟彈窗
this.dialog = true;
// 標記為新增
this.isInsert = true;
})
})
}
}
重點是編寫後臺介面,返回資料即可。
2.2.2 後端介面
Pojo
package com.leyou.item.pojo;
import javax.persistence.Id;
import javax.persistence.Table;
@Table(name = "tb_specification")
public class Specification {
@Id
private Long categoryId;
private String specifications;
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
public String getSpecifications() {
return specifications;
}
public void setSpecifications(String specifications) {
this.specifications = specifications;
}
@Override
public String toString() {
return "Specification{" +
"categoryId=" + categoryId +
", specifications='" + specifications + '\'' +
'}';
}
}
Mapper
package com.leyou.item.mapper;
import com.leyou.item.pojo.Specification;
import tk.mybatis.mapper.common.Mapper;
/**
* @author li
*/
@org.apache.ibatis.annotations.Mapper
public interface SpecificationMapper extends Mapper<Specification> {
}
Controller
先分析下需要的東西,在頁面的ajax請求中可以看出:
-
請求方式:查詢,肯定是get
-
請求路徑:/spec/{cid} ,這裡通過路徑佔位符傳遞商品分類的id
-
請求引數:商品分類id
-
返回結果:頁面是直接把
resp.data
賦值給了specifications:
那麼返回的應該是規格引數的字串
package com.leyou.item.controller;
import com.leyou.item.pojo.Specification;
import com.leyou.item.service.SpecificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* @author li
*/
@RestController
@RequestMapping("spec")
public class SpecificationController {
@Autowired
private SpecificationService specificationService;
/**
* 查詢商品分類對應的規格引數模板
* @param id
* @return
*/
@GetMapping("{id}")
public ResponseEntity<String> querySpecificationByCategoryId(@PathVariable("id") Long id){
Specification spec = this.specificationService.queryById(id);
if (spec == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return ResponseEntity.ok(spec.getSpecifications());
}
}
Service
介面
package com.leyou.item.service;
import com.leyou.item.pojo.Specification;
/**
* @Author: 98050
* Time: 2018-08-14 15:26
* Feature:
*/
public interface SpecificationService {
/**
* 根據category id查詢規格引數模板
* @param id
* @return
*/
Specification queryById(Long id);
}
實現類
package com.leyou.item.service.serviceimpl;
import com.leyou.item.mapper.SpecificationMapper;
import com.leyou.item.pojo.Specification;
import com.leyou.item.service.SpecificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Author: 98050
* Time: 2018-08-14 15:26
* Feature:
*/
@Service
public class SpecificationServiceImpl implements SpecificationService {
@Autowired
private SpecificationMapper specificationMapper;
@Override
public Specification queryById(Long id) {
return this.specificationMapper.selectByPrimaryKey(id);
}
}
測試訪問
資料庫中由3條完整模板資訊
頁面訪問:
規格引數的增加、刪除和修改全部在這個對話方塊中完成,所以刪除和修改就可以合併。
注:每一個分組的全部資料後期都以JSON的方式存入
2.3 規格引數增加
2.3.1 前端程式碼
//新增分組
addGroup() {
this.specifications.push({
group: '',
params: []
})
},
// 新增新屬性
addParam(i) {
this.specifications[i].params.push({
k: "",
searchable: false,
global: true,
numerical:false,
unit:"",
options: []
})
},
// 為屬性新增預設值
addOption(i, j) {
this.specifications[i].params[j].options.push("")
},
上面這三個函式主要是用來新增分組、屬性、屬性預設值,演示如下所示:
重點就是saveTemplate函數了,包含對規格模板的增加、修改和刪除等功能。
// 儲存、修改、刪除模板
saveTemplate() {
this.dialog = true;
//模板刪除
if (this.specifications.length === 0){
//console.log("刪除:"+this.currentNode.id);
this.$http.delete("/item/spec/"+this.currentNode.id).then(() => {
this.dialog = false;
this.$message.success("刪除成功!");
this.oldSpec = [];
}).catch(() => {
this.$message.error("刪除失敗");
});
}else {
this.$http({
method: this.oldSpec.length === 0 ? 'post' : 'put',
url: '/item/spec',
data: this.$qs.stringify({
categoryId: this.currentNode.id,
specifications: JSON.stringify(this.specifications)
})
})
.then(() => {
this.dialog = false;
this.$message.success("儲存成功!")
this.oldSpec = [];
})
.catch(() => {
this.$message.error("儲存失敗!")
});
}
}
注:
如何判斷是否進行刪除?通過specifications的長度來判斷是否進行修改,長度為0表示模板內容為空。
如何判斷修改還是新增? 通過oldSpec的長度來判斷,因為在點選葉子節點時會查詢對應的規格模板,查詢到的話就將資料儲存一份到oldSpec中,用來進行修改;所以當oldSpec的長度為0時,那麼說明沒有查到模板,即為新增。(oldSpec是個陣列)
2.3.2 後臺介面
Controller
/**
* 儲存一個規格引數模板
* @param specification
* @return
*/
@PostMapping
public ResponseEntity<Void> saveSpecification(Specification specification){
this.specificationService.saveSpecification(specification);
return ResponseEntity.status(HttpStatus.OK).build();
}
Mapper
package com.leyou.item.mapper;
import com.leyou.item.pojo.Specification;
import tk.mybatis.mapper.common.Mapper;
/**
* @author li
*/
@org.apache.ibatis.annotations.Mapper
public interface SpecificationMapper extends Mapper<Specification> {
}
Service
介面
/**
* 新增規格引數模板
* @param specification
*/
void saveSpecification(Specification specification);
實現類
@Override
public void saveSpecification(Specification specification) {
this.specificationMapper.insert(specification);
}
2.4 規格引數修改
Controller
/**
* 修改一個規格引數模板
* @param specification
* @return
*/
@PutMapping
public ResponseEntity<Void> updateSpecification(Specification specification){
this.specificationService.updateSpecification(specification);
return ResponseEntity.status(HttpStatus.OK).build();
}
Mapper
package com.leyou.item.mapper;
import com.leyou.item.pojo.Specification;
import tk.mybatis.mapper.common.Mapper;
/**
* @author li
*/
@org.apache.ibatis.annotations.Mapper
public interface SpecificationMapper extends Mapper<Specification> {
}
Service
介面
/**
* 修改規格引數模板
* @param specification
*/
void updateSpecification(Specification specification);
實現類
@Override
public void updateSpecification(Specification specification) {
this.specificationMapper.updateByPrimaryKeySelective(specification);
}
2.5 規格引數刪除
Controller
@DeleteMapping("{id}")
public ResponseEntity<Void> deleteSpecification(@PathVariable("id") Long id){
Specification specification = new Specification();
specification.setCategoryId(id);
this.specificationService.deleteSpecification(specification);
return ResponseEntity.status(HttpStatus.OK).build();
}
Mapper
package com.leyou.item.mapper;
import com.leyou.item.pojo.Specification;
import tk.mybatis.mapper.common.Mapper;
/**
* @author li
*/
@org.apache.ibatis.annotations.Mapper
public interface SpecificationMapper extends Mapper<Specification> {
}
Service
介面
/**
* 刪除規格引數模板
* @param specification
*/
void deleteSpecification(Specification specification);
實現類
@Override
public void deleteSpecification(Specification specification) {
this.specificationMapper.deleteByPrimaryKey(specification);
}
2.6 總結
對規格模板的操作都是放在一個對話方塊中進行的,因為specifications中儲存的是全部的分組資訊,每個分組的資訊都是JSON格式。新增和修改就是直接把specifications中的內容傳到後臺進行相應的操作,刪除則是通過判斷specifications的長度來決定的,當長度為0時,說明使用者已經把所有分組全部刪除,所以相應的在資料庫中要清除對應id的資料。刪除是通過給後臺傳入id進行的,那麼這個id從何而來,通過this.currentNode.id來獲取。
2.7 測試
相關推薦
樂優商城(七)商品規格管理
目錄 2.6 總結 2.7 測試 商品規格管理 一、商品規格資料結構 樂優商城是一個全品類的電商網站,因此商品的種類繁多,每一件商品,其屬性又有差別。為了更準確描述商品及細分差別,抽象出兩個
樂優商城(三)商品分類管理
目錄 一、資料 後臺功能——商品分類管理 商城中最重要的就是商品,當商品數目增多後,需要對商品進行分類,而且不同的商品會有不同的品牌資訊。具體關係如下圖所示: 一個商品分
樂優商城(十)商品管理
目錄 3.4 商品描述資訊 商品描述資訊比較複雜,而且圖文並茂,甚至包括視訊。 這樣的內容,一般都會使用富文字編輯器。 3.4.1 富文字編輯器 通俗來說:富文字,就是比較豐富的文字編輯器。
樂優商城(五)品牌管理(後端)
目錄 後臺功能——品牌管理(後端) 二、後端介面實現 主要就是對資料庫的抽插,難點在於和前端頁面的聯調,介面本身不復雜。 2.1 品牌查詢 2.1.1 資料庫表
樂優商城(最終)
為期一個月的樂優商城的學習結束了。。這個月真的收穫挺多的,之前學習一直都是很零散的知識。沒有很正做專案。現在,做完這一個月的專案。寫了這麼多天的部落格,中間一度產生了厭倦,不想再寫部落格了,可是,想到有始有終吧,還是堅持把部落格寫完。接下來,就是準備知識,要出去面試了。一共9
樂優商城(三十七)——訂單微服務
目錄 四、細節優化 4.1 支付頁面顯示總金額 4.1.1 支付頁面 4.1.2 支付成功頁面 4.2 修改訂單號的傳遞方式 4.2.1 修改訂單提交函式 4.2.2 修改支付頁面 4.3 訂單提交時進行登入認證 4.4 本地資料刪除 4.5 購物車資料更新
樂優商城(二十三)——商品詳情及靜態化
目錄 2.1 簡介 二、頁面靜態化 2.1 簡介 2.1.1 問題分析 現在的頁面是通過Thymeleaf模板引擎渲染後返回到客戶端。在後臺需要大量的資料查詢,而後渲染得到HTML頁面。會對資料庫造成壓力,並且請求的響應時間過
樂優商城(填坑)——秒殺商品新增
一、需求 後臺商品管理中,將商品新增到可秒殺商品列表 選中商品將其設定為可秒殺。 選擇具體的參與秒殺的商品規格,然後設定相關引數,點選儲存即可。 二、後端介面修改 原來的新增秒殺商品介面在leyou-secskill微服務中,現在將其移動到leyou-item中,程式
樂優商城(四十七)秒殺總結
目錄 一、快取優化 1.1 頁面快取 將不經常改動的頁面直接快取到redis中,然後用Thymeleaf檢視解析器將快取的頁面直接渲染出來。 1.2 物件快取 將經常使用的物件資訊放入redis中,比如
樂優商城(十一)商品管理
目錄 3.8 表單提交 3.8.1 新增提交按鈕和動畫 樣式: <v-layout row> <v-spacer></v-spacer> <v-btn
樂優商城(十二)商品管理
目錄 4.3 效果 4.5 測試 5.3 測試 6.2 前端 6.4 測試 四、商品修改 4.1 點選編輯出現彈窗 4.2 回顯資料 回顯資料就是把當前點選
樂優商城(三十九)—— 訂單中心
目錄 一、我的訂單頁 1.1. 頁面效果 1.2 後臺介面 1.3 頁面改造 1.3.1 資料載入 1.3.2 分頁條 1.4 測試 1.5 訂單狀態過濾 1.5.1 全部訂單(16) 1.5.2 待付款(3) 1.5.3 待發貨(4) 1.5.4 待
樂優商城(三十八)——訂單微服務
目錄 五、地址管理 5.1 頁面效果 5.2 資料庫表設計 5.3 頁面優化 5.3.1 在data中定義資料 5.3.2 模態框 5.3.3 方法繫結 5.3.4 效果展示 5.4 後臺介面 5.4.1 實體類 5
樂優商城(三十六)——訂單微服務
目錄 二、訂單結算頁 2.1 頁面跳轉 2.2 收貨人資訊 2.3 支付方式 2.4 商品列表 2.4.1 購物車資訊獲取 2.4.2 頁面渲染 2.5 總金額 2.6 提交訂單 2.6.1 頁面提交 2.6.2 精度損失問題 三、微信支付 3.1
樂優商城(三十四)——訂單微服務
目錄 一、建立訂單微服務 1.1 建立module 1.2 pom依賴 1.3 配置檔案 1.4 啟動類 1.5 配置匯入 1.6 屬性讀取 1.7 支付工具類 1.8 修改閘道器配置 二、實體類準備 2.1 Order.java 2.2 OrderD
樂優商城(三十五)——訂單微服務
目錄 一、訂單系統介面 1.1 Swagger-UI 1.1.1 什麼是OpenApi 1.1.2 什麼是Swagger 1.1.3 快速入門 1.2 測試介面 1.2.1 建立訂單介面 1.2.2 生成ID方式 1.2.3 查詢訂單介面
樂優商城(三十三)——購物車
目錄 四、已登入購物車 4.1 新增登入校驗 4.1.1 引入JWT相關依賴 4.1.2 配置公鑰 4.1.3 載入公鑰 4.1.4 編寫攔截器 4.1.5 配置攔截器 4.1.6 編寫過濾器 4.1.7 配置過濾器 4.2 後臺購物車設計 4.3 新增商
樂優商城(三十二)——購物車
目錄 一、搭建購物車微服務 1.1 建立module 1.2 pom依賴 1.3 配置檔案 1.4 啟動類 二、購物車功能分析 2.1 需求 2.2 流程圖 三、未登入購物車 3.1 準備 3.1.1 購物車的資料結構 3.1.2 web本地儲存
樂優商城(三十一)——授權中心
目錄 三、首頁判斷登入狀態 3.1 頁面程式碼 3.2 後臺實現校驗使用者介面 3.3 測試 3.4 重新整理token 四、閘道器的登入攔截 4.1 引入jwt相關配置 4.2 編寫過濾邏輯 4.3 白名單 三、首頁判斷登入狀態 雖然cookie已經
樂優商城(三十)——授權中心
目錄 一、無狀態登入原理 1.1 什麼是有狀態 1.2 什麼是無狀態 1.3 如何實現無狀態 1.4 JWT 1.4.1 簡介 1.4.2 資料格式 1.4.3 JWT互動流程 1.4.4 非對稱加密 1.5 結合Zuul的鑑權流程 1.5.1 沒有RSA