一文搞懂大廠商品中心設(shè)計(jì)!
1 雪花算法使用
IdWorker idWorker=new IdWorker(1,1);
for(int i=0;i<10000;i++){
    long id = idWorker.nextId();
    System.out.println(id);
}配置分布式ID生成器
- 將IdWorker.java拷貝到util包
 - 在工程的resources下新增applicationContext-service.xml
 
<!‐‐雪花ID生成器‐‐>
<bean id="idWorker" class="com.qingcheng.util.IdWorker">
    <constructor‐arg index="0" value="1"></constructor‐arg>
    <constructor‐arg index="1" value="1"></constructor‐arg>
</bean>2 新增和修改商品
2.1 表結(jié)構(gòu)
t_spu 表
字段名稱  | 字段含義  | 字段類型  | 字段長(zhǎng)度  | 備注  | 
id  | 主鍵  | VARCHAR  | ||
sn  | 貨號(hào)  | VARCHAR  | ||
name  | SPU名  | VARCHAR  | ||
caption  | 副標(biāo)題  | VARCHAR  | ||
brand_id  | 品牌ID  | INT  | ||
category1_id  | 一級(jí)分類  | INT  | ||
category2_id  | 二級(jí)分類  | INT  | ||
category3_id  | 三級(jí)分類  | INT  | ||
template_id  | 模板ID  | INT  | ||
freight_id  | 運(yùn)費(fèi)模板id  | INT  | ||
image  | 圖片  | VARCHAR  | ||
images  | 圖片列表  | VARCHAR  | ||
sale_service  | 售后服務(wù)  | VARCHAR  | ||
introduction  | 介紹  | TEXT  | ||
spec_items  | 規(guī)格列表  | VARCHAR  | ||
para_items  | 參數(shù)列表  | VARCHAR  | ||
sale_num  | 銷量  | INT  | ||
comment_num  | 評(píng)論數(shù)  | INT  | ||
is_marketable  | 是否上架  | CHAR  | ||
is_enable_spec  | 是否啟用規(guī)格  | CHAR  | ||
is_delete  | 是否刪除  | CHAR  | ||
status  | 審核狀態(tài)  | CHAR  | 
t_sku 表
字段名稱  | 字段含義  | 字段類型  | 字段長(zhǎng)度  | 備注  | 
id  | 商品id  | VARCHAR  | ||
sn  | 商品條碼  | VARCHAR  | ||
name  | SKU名稱  | VARCHAR  | ||
price  | 價(jià)格(分)  | INT  | ||
num  | 庫(kù)存數(shù)量  | INT  | ||
alert_num  | 庫(kù)存預(yù)警數(shù)量  | INT  | ||
image  | 商品圖片  | VARCHAR  | ||
images  | 商品圖片列表  | VARCHAR  | ||
weight  | 重量(克)  | INT  | ||
create_time  | 創(chuàng)建時(shí)間  | DATETIME  | ||
update_time  | 更新時(shí)間  | DATETIME  | ||
spu_id  | SPUID  | VARCHAR  | ||
category_id  | 類目ID  | INT  | ||
category_name  | 類目名稱  | VARCHAR  | ||
brand_name  | 品牌名稱  | VARCHAR  | ||
spec  | 規(guī)格  | VARCHAR  | ||
sale_num  | 銷量  | INT  | ||
comment_num  | 評(píng)論數(shù)  | INT  | ||
status  | 商品狀態(tài) 1-正常,2-下架,3-刪除  | CHAR  | 
2.2 需求與實(shí)現(xiàn)
2.2.1 需求分析
詳見靜態(tài)原型。
2.2.2 實(shí)現(xiàn)思路
前端傳遞給后端的數(shù)據(jù)格式:
{
    "spu": {
        "name": "這個(gè)是商品名稱",
        "caption": "這個(gè)是副標(biāo)題",
        "brandId": 12,
        "category1Id": 558,
        "category2Id": 559,
        "category3Id": 560,
        "freightId": 10,
        "image": "http://www.qingcheng.com/image/1.jpg",
        "images": "http://www.qingcheng.com/image/1.jpg,http://www.qingcheng.com/image/2.jpg",
        "introduction": "這個(gè)是商品詳情,html代碼",
        "paraItems": {
            "出廠年份": "2019",
            "贈(zèng)品": "充電器"
        },
        "saleService": "七天包退,閃電退貨",
        "sn": "020102331",
        "specItems": {
            "顏色": [
                "紅",
                "綠"
            ],
            "機(jī)身內(nèi)存": [
                "64G",
                "8G"
            ]
        },
        "templateId": 42
    },
    "skuList": [
        {
            "sn": "10192010292",
            "num": 100,
            "alertNum": 20,
            "price": 900000,
            "spec": {
                "顏色": "紅",
                "機(jī)身內(nèi)存": "64G"
            },
            "image": "http://www.qingcheng.com/image/1.jpg",
            "images": "http://www.qingcheng.com/image/1.jpg,http://www.qingcheng.com/image/2.jpg",
            "status": "1",
            "weight": 130
        },
        {
            "sn": "10192010293",
            "num": 100,
            "alertNum": 20,
            "price": 600000,
            "spec": {
                "顏色": "綠",
                "機(jī)身內(nèi)存": "8G"
            },
            "image": "http://www.qingcheng.com/image/1.jpg",
            "images": "http://www.qingcheng.com/image/1.jpg,http://www.qingcheng.com/image/2.jpg",
            "status": "1",
            "weight": 130
        }
    ]
}2.3 代碼實(shí)現(xiàn)
2.3.1 SPU與SKU列表的保存
/**
* 商品組合實(shí)體類 
*/
public class Goods implements Serializable {
    private Spu spu;
    private List<Sku> skuList;
}SpuServiceImpl新增方法:
@Autowired
private SkuMapper skuMapper;
@Autowired  
private IdWorker idWorker;
@Autowired
private CategoryMapper categoryMapper;
/**
* 保存商品
* @param goods 商品組合實(shí)體類
*/
@Transactional
public void saveGoods(Goods goods) {
    // 保存一個(gè)spu的信息
    Spu spu = goods.getSpu();
    spu.setId(idWorker.nextId()+"");
    spuMapper.insert(spu);
    
    //保存sku列表的信息
    Date date=new Date();
    //分類對(duì)象
    Category category = categoryMapper.selectByPrimaryKey(spu.getCategory3Id());
    List<Sku> skuList = goods.getSkuList();
    for (Sku sku:skuList){
        sku.setId(idWorker.nextId()+"");
        sku.setSpuId(spu.getId());
        //sku名稱 =spu名稱+規(guī)格值列表
        String name=spu.getName();
        //sku.getSpec() {"顏色":"紅","機(jī)身內(nèi)存":"64G"}
        Map<String,String> specMap = JSON.parseObject(sku.getSpec(), Map.class);
        for(String value:specMap.values()){
            name+=" "+value;
        }
        sku.setName(name);//名稱
        sku.setCreateTime(date);//創(chuàng)建日期
        sku.setUpdateTime(date);//修改日期
        sku.setCategoryId(spu.getCategory3Id());//分類id
        sku.setCategoryName(category.getName());//分類名稱
        sku.setCommentNum(0);//評(píng)論數(shù)
        sku.setSaleNum(0);//銷售數(shù)量
        skuMapper.insert(sku);
    }
}該方法要對(duì)兩個(gè)表操作,需添加事務(wù):
@Service(interfaceClass=SpuService.class)在類上加@Transactional,并在@Service注解中指定接口為SpuService.class。
SpuController修改add方法:
@PostMapping("/save")
public Result save(@RequestBody Goods goods){
    spuService.saveGoods(goods);
    return new Result();
}3 建立分類與品牌的關(guān)聯(lián)
3.1 需求
Q:為什么要建立分類與品牌的關(guān)聯(lián)?
A:因?yàn)槲覀冊(cè)谇芭_(tái)搜索時(shí),需要通過(guò)分類找到品牌列表。
Q:分類與品牌是什么關(guān)系?
A:多對(duì)多。
Q:在什么地方添加關(guān)系?
A:我們不在后臺(tái)單獨(dú)實(shí)現(xiàn)分類與品牌的關(guān)聯(lián),而是在添加商品時(shí)自動(dòng)添加關(guān)聯(lián)。
3.2 實(shí)現(xiàn)思路
- 設(shè)計(jì)中間表tb_category_brand表
 - 創(chuàng)建實(shí)體類、數(shù)據(jù)訪問(wèn)接口
 - 在添加商品的saveGoods方法中添加代碼邏輯 ,將SPU的品牌編號(hào)和分類編號(hào)一起插入到(中間表)
 
3.3 代碼
創(chuàng)建實(shí)體類
@Table(name="tb_category_brand")
@Data
public class CategoryBrand implements Serializable {
    @Id
    private Integer categoryId;
    @Id 
    private Integer brandId;
}聯(lián)合主鍵,templateId和brandId都有@Id注解。
新建數(shù)據(jù)訪問(wèn)接口
public interface CategoryBrandMapper extends Mapper<CategoryBrand> {
}修改saveGoods方法
CategoryBrand categoryBrand =new CategoryBrand();
categoryBrand.setBrandId(spu.getBrandId());
categoryBrand.setCategoryId(spu.getCategory3Id());
int count=categoryBrandMapper.selectCount(categoryBrand);
if(count==0) {
    categoryBrandMapper.insert(categoryBrand);
}4 根據(jù)ID查詢商品
根據(jù)id 查詢SPU和SKU列表 ,顯示效果:
{
    "spu": {
        "brandId": 0,
        "caption": "111",
        "category1Id": 558,
        "category2Id": 559,
        "category3Id": 560,
        "commentNum": null,
        "freightId": null,
        "id": 149187842867993,
        "image": null,
        "images": null,
        "introduction": null,
        "isDelete": null,
        "isEnableSpec": "0",
        "isMarketable": "1",
        "name": "黑馬智能手機(jī)",
        "paraItems": null,
        "saleNum": null,
        "saleService": null,
        "sn": null,
        "specItems": null,
        "status": null,
        "templateId": 42
    },
    "skuList": [
        {
            "alertNum": null,
            "brandName": "金立(Gionee)",
            "categoryId": 560,
            "categoryName": "手機(jī)",
            "commentNum": null,
            "createTime": "2018‐11‐06 10:17:08",
            "id": 1369324,
            "image": null,
            "images": "blob:http://localhost:8080/ec04d1a5‐d865‐4e7f‐a313‐2e9a76cfb3f8",
            "name": "黑馬智能手機(jī)",
            "num": 100,
            "price": 900000,
            "saleNum": null,
            "sn": "",
            "spec": null,
            "spuId": 149187842867993,
            "status": "1",
            "updateTime": "2018‐11‐06 10:17:08",
            "weight": null
        },
        {
            "alertNum": null,
            "brandName": "金立(Gionee)",
            "categoryId": 560,
            "categoryName": "手機(jī)",
            "commentNum": null,
            "createTime": "2018‐11‐06 10:17:08",
            "id": 1369325,
            "image": null,
            "images": "blob:http://localhost:8080/ec04d1a5‐d865‐4e7f‐a313‐2e9a76cfb3f8",
            "name": "黑馬智能手機(jī)",
            "num": 100,
            "price": 900000,
            "saleNum": null,
            "sn": "",
            "spec": null,
            "spuId": 149187842867993,
            "status": "1",
            "updateTime": "2018‐11‐06 10:17:08",
            "weight": null
        }
    ]
}4.1 代碼
SpuService方法
/**
* 根據(jù)ID查詢商品
* @param id 
* @return
*/
public Goods findGoodsById(String id){
    //查詢spu
    Spu spu = spuMapper.selectByPrimaryKey(id);
    
    //查詢SKU 列表
    Example example=new Example(Sku.class);
    Example.Criteria criteria = example.createCriteria();
    criteria.andEqualTo("spuId",id);
    List<Sku> skuList = skuMapper.selectByExample(example);
    
    //封裝,返回
    Goods goods=new Goods();
    goods.setSpu(spu);
    goods.setSkuList(skuList);
    return goods;
}SpuController
@GetMapping("/findGoodsById")
public Goods findGoodsById(Long id){
    return spuService.findGoodsById(id);
}5 保存修改
實(shí)現(xiàn)思路
- 修改與新增共用同一個(gè)方法
 - 通過(guò)spu的id判斷當(dāng)前操作是新增還是修改
 - 如果是新增需要設(shè)置spu的id,對(duì)spu執(zhí)行的是insert操作
 - 如果修改則需要?jiǎng)h除原來(lái)的sku列表,對(duì)spu執(zhí)行的是updateByPrimaryKeySelective操作
 - sku列表的插入部分的代碼要判斷sku是否有id,如果有id則不重新生成id
 
代碼實(shí)現(xiàn)
修改SpuServiceImpl的saveGoods:
/**
* 保存商品
* @param goods
*/
@Transactional
public void saveGoods(Goods goods) {
    //保存一個(gè)spu的信息
    Spu spu = goods.getSpu();
    if(spu.getId()==null){//新增商品
        spu.setId(idWorker.nextId()+"");
        spuMapper.insert(spu);
    }else{ //修改
        //刪除原來(lái)的sku列表
        Example example=new Example(Sku.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("spuId",spu.getId());
        skuMapper.deleteByExample(example);
        //執(zhí)行spu的修改
        spuMapper.updateByPrimaryKeySelective(spu);
    }
    //保存sku列表的信息
    List<Sku> skuList = goods.getSkuList();
    for (Sku sku:skuList){
        if(sku.getId()==null){//新增
            sku.setId(idWorker.nextId()+"");
            sku.setCreateTime(date);//創(chuàng)建日期
        }
        //添加sku
    }
//建立分類和品牌的關(guān)聯(lián)
}6 未啟用規(guī)格的sku處理
6.1 需求分析
有些商品沒區(qū)分規(guī)格,即一個(gè)spu對(duì)應(yīng)一個(gè)sku ,這時(shí)sku列表只傳遞一條記錄,且無(wú)規(guī)格(spec)屬性,要對(duì)其判斷,避免因空值產(chǎn)生異常。
6.2 實(shí)現(xiàn)思路
在saveGoods方法的sku列表循環(huán)中添加代碼,判斷
// 構(gòu)建SKU名稱,采用SPU+規(guī)格值組裝
if(sku.getSpec()==null || "".equals(sku.getSpec())){
    sku.setSpec("{}");
}7 商品審核與上下架
7.1 商品審核
7.1.1 需求分析與實(shí)現(xiàn)思路
商品審核:新錄入的商品是未審核狀態(tài),也是未上架狀態(tài)。
實(shí)現(xiàn)思路
- 修改審核狀態(tài),如果審核狀態(tài)為1,則上架狀態(tài)也修改為1
 - 記錄商品審核記錄
 - 記錄商品日志
 
7.1.2 代碼實(shí)現(xiàn)
/**
* 商品審核
* @param id
* @param status
* @param message
*/
@Transactional
public void audit(String id, String status, String message) {
    //1.修改狀態(tài) 審核狀態(tài)和上架狀態(tài)
    Spu spu = new Spu();
    spu.setId(id);
    spu.setStatus(status);
    if("1".equals(status)){//審核通過(guò)
        spu.setIsMarketable("1");//自動(dòng)上架
    }
    spuMapper.updateByPrimaryKeySelective(spu);
    
    //2.記錄商品審核記錄
    
    //3.記錄商品日志
}@GetMapping("/audit")
public Result audit(Long id){
    spuService.audit(id);
    return new Result();
}7.2 下架商品
7.2.1 需求與實(shí)現(xiàn)思路
下架商品,修改上下架狀態(tài)為下架。下架商品不修改審核狀態(tài)。 下架商品需要記錄商品日志。
7.2.2 代碼實(shí)現(xiàn)
/**
* 下架商品
* @param id
*/
public void pull(String id) {
    Spu spu = spuMapper.selectByPrimaryKey(id);
    spu.setIsMarketable("0");//下架狀態(tài)
    spuMapper.updateByPrimaryKeySelective(spu);
}@GetMapping("/pull")
public Result pull(String id){
    spuService.pull(id);
    return new Result();
}7.3 上架商品
7.3.1 需求分析
將商品修改為上架狀態(tài),需要驗(yàn)證該商品是否審核通過(guò),未審核通過(guò)的商品不能上架。 上架商品需要記錄商品日志。
7.3.2 代碼實(shí)現(xiàn)
通過(guò)審核的商品才能上架:
/**
* 商品上架
* @param id
*/
public void put(String id) {
    //1.修改狀態(tài)
    Spu spu = spuMapper.selectByPrimaryKey(id);
    if(!"1".equals(spu.getStatus())){
        throw new RuntimeException("此商品未通過(guò)審核");
    }
    spu.setIsMarketable("1");
    spuMapper.updateByPrimaryKeySelective(spu);
    
    //2.記錄商品日志
}@GetMapping("/put")
public Result put(String id){
    spuService.put(id);
    return new Result();
}7.4 批量上下架
7.4.1 需求分析
前端傳遞一組商品ID,后端進(jìn)行批量上下架處理,處理后給前端返回處理的條數(shù)
7.4.2 代碼實(shí)現(xiàn)
/**
* 批量上架商品
* @param ids
*/
public int putMany(Long[] ids) {
    Spu spu=new Spu();
    spu.setIsMarketable("1");//上架
    
    //批量修改
    Example example=new Example(Spu.class);
    Example.Criteria criteria = example.createCriteria();
    criteria.andIn("id", Arrays.asList(ids));//id
    criteria.andEqualTo("isMarketable","0");//下架
    criteria.andEqualTo("status","1");//審核通過(guò)的
    criteria.andEqualTo("isDelete","0");//非刪除的
    return spuMapper.updateByExampleSelective(spu, example);
}@GetMapping("/putMany")
public Result putMany(Long[] ids){
    int count = spuService.putMany(ids);
    return new Result(0,"上架"+count+"個(gè)商品");
}8 刪除與還原商品
8.1 需求分析
刪除商品并非物理刪除(真正的執(zhí)行刪除數(shù)據(jù)),而是通過(guò)將表中某字段標(biāo)記為刪除狀態(tài)。
還原商品實(shí)際就是將刪除狀態(tài)再修改回來(lái)。
如果商品需要物理刪除,必須是先邏輯刪除才能進(jìn)行物理刪除,刪除前需要檢查狀態(tài)。
8.2 實(shí)現(xiàn)思路
- 邏輯刪除商品,修改spu表is_delete字段為1
 - 商品回收站顯示spu表is_delete字段為1的記錄
 - 回收商品,修改spu表is_delete字段為0
 















 
 
 

















 
 
 
 