javaweb-青橙项目-4-79

第4章 商品管理后端逻辑

学习目标

掌握分布式ID生成器(snowflake)的使用
完成新增和修改商品的后端逻辑
完成商品审核与上下架的后端逻辑
完成商品删除与还原的后端逻辑

前端并非是重点,此后重点转移到后端开发
序列-4:https://github.com/Jonekaka/javaweb-qingcheng-4-79

1. 分布式ID生成解决方案

1.1 数据库分片

对于海量增长的数据,各种操作越加困难,
此时就需要做数据库集群,将一个数据库的数据分散到不同的数据库中存储,即数据库分片。
如何实现数据库分片?
mycat数据库中间件

MyCat是一个开源的分布式数据库系统,实现了MySQL协议
前端用户可以把它看作是一个数据库代理,用MySQL客户端工具和命令行访问,
而其后端可以用MySQL原生协议与多个MySQL服务器通信,也可以用JDBC协议与大多数主流数据库服务器通信,
其核心功能是分表分库,即将一个大表水平分割为N个小表,存储在后端MySQL服务器里或者其他数据库里。

MyCat发展到目前的版本,它的后端可以支持MySQL、SQL Server、Oracle、DB2、PostgreSQL等主流数据库,也支持MongoDB这种新型NoSQL方式的存储,未来还会支持更多类型的存储。
在这里插入图片描述
将一个大表水平分割为N个小表
应用并不需要做任何改变,mycat隐藏了数据库分表的细节
mycat由运维管理

1.2 分布式ID生成解决方案

数据库的表已经分别存储在各个服务器上了,要避免重复id问题
因此有如下三种id方案

1.2.1 UUID

常见的方式。可以利用数据库也可以利用程序生成,一般来说全球唯一。
优点:
1)简单,代码方便。
2)生成ID性能非常好,基本不会有性能问题。
3)全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应
对。
缺点:
1)没有排序,无法保证趋势递增。
2)UUID往往是使用字符串存储,查询的效率比较低。
3)存储空间比较大,如果是海量数据库,就需要考虑存储量的问题。
4)传输数据量大
5)不可读。

1.2.2 Redis生成ID

数据库服务器向redis请求分配id
使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作
INCR和INCRBY来实现。
优点:
1)不依赖于数据库,灵活方便,且性能优于数据库。
2)数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:
1)如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。
2)需要编码和配置的工作量比较大。
3)网络传输造成性能下降。

1.2.3 开源算法snowflake

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。
其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器
ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0
在这里插入图片描述
本次采用开源算法snowflake

1.3 snowflake快速入门

1.3.1 快速入门

(1)新建工程,将IdWorker.java拷贝到工程中。
(2)编写代码
数据中心,机器id初始化为11,默认00
在这里插入图片描述

IdWorker idWorker=new IdWorker(1,1);
        for(int i=0;i<10000;i++){
        long id = idWorker.nextId();
        System.out.println(id);
        }

1.3.2 配置分布式ID生成器

(1)将IdWorker.java放到qingcheng_common_service工程com.qingcheng.util包中
(2)在qingcheng_service_goods工程resources下新增applicationContextservice.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 概念与表结构分析

2.1.1 SPU与SKU

SPU = Standard Product Unit (标准产品单位)
SPU是商品信息聚合的最小单位,描述了一个产品的特性。是一组可复用、易检索的标准化信息的集合
通俗点讲,属性值、特性相同的商品就可以称为一个SPU。
例如:
华为P20 pro就是一个SPU,与商家,与颜色、款式、套餐都无关。

SKU=stock keeping unit(库存量单位)
SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位。
SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。在服装、鞋类商品中使用最多最普遍。
例如:
华为P20 pro 宝石蓝 64G
华为P20 pro 亮黑色 64G
一对多的关系

2.1.2 表结构分析

货号,商品的条码,仓库使用,id被隐藏,不同的系统中还可能不同,但是货号存在且一致
spu名字,商品名字,比如华为p20
副标题存储一般促销信息
模板id,一般商品有信息模板
运费模板,和运费相关
图片,spu的封面
图片s,其他图片
tb_spu 表

字段名称字段含义字段类型字段长度备注
id主键VARCHAR
sn货号VARCHAR
nameSPU名VARCHAR
caption副标题VARCHAR
brand_id品牌IDINT
category1_id一级分类INT
category2_id二级分类INT
category3_id三级分类INT
template_id模板IDINT
freight_id运费模板idINT
image图片VARCHAR
images图片列表VARCHAR
sale_service售后服务VARCHAR
introduction介绍TEXT
spec_items规格列表VARCHAR
para_items参数列表VARCHAR
sale_num销量INT
comment_num评论数INT
is_marketable是否上架CHAR
is_enable_spec是否启用规格CHAR
is_delete是否删除CHAR
status审核状态CHAR

sn,同样对接仓库用的
tb_sku 表
金额相关用int,单位分,不用小数是不担心四舍五入,而且存储方便,显示时只需要/100就行
sku会;用到spu的图片,因为是一对多关系,spu被sku分享
spuid,sku与spu的绑定关系
类目id,三级分类
品牌名称

字段名称字段含义字段类型字段长度备注
id商品idVARCHAR
sn商品条码VARCHAR
nameSKU名称VARCHAR
price价格(分)INT
num库存数量INT
alert_num库存预警数量INT
image商品图片VARCHAR
images商品图片列表VARCHAR
weight重量(克)INT
create_time创建时间DATETIME
update_time更新时间DATETIME
spu_idSPUIDVARCHAR
category_id类目IDINT
category_name类目名称VARCHAR
brand_name品牌名称VARCHAR
spec规格VARCHAR
sale_num销量INT
comment_num评论数INT
status商品状态 1-正常,2-下架,3-删除CHAR

2.2 需求与实现思路

2.2.1 需求分析

点击分类列表,二级,三级依次刷新
需求详见静态原型

填写商品信息,如数据库所描述
类似于编写spu信息
在这里插入图片描述
商品编号可以用来和其他系统做对接
在这里插入图片描述
sku信息编写
当选择商品属性时,下面自动新增条目,spu开始分裂出sku
在这里插入图片描述
输入sku的具体参数,比如图片,比如时间信息
这些图片是spu的
sku的图片在上面介绍尺码时上传
在这里插入图片描述
提交审核,
在这里插入图片描述
此时在spu表中添加一条数据,在sku表中添加多条数据,
提交后的商品进入审核状态
在这里插入图片描述

2.2.2 实现思路

前端传递给后端的数据格式
spu
sku信息
分类
图片上对应的选项与信息封装成json发送到后端,依次进入数据库

{
"spu": {
"name": "这个是商品名称",
"caption": "这个是副标题",
"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.jp
g",
"introduction": "这个是商品详情,html代码",
"paraItems": {"出厂年份":"2019","赠品":"充电器"},
"saleService": "七天包退,闪电退货",
"sn": "020102331",
"specItems": {"颜色":["红","绿"],"机身内存":["64G","8G"]},
"templateId": 42
},


"skuList": [{
"sn": "10192010292",
"num": 100,
"alertNum": 20,
"price": 900000,
"spec": {"颜色":"红","机身内存":"64G"},
"image": "http://www.qingcheng.com/image/1.jpg",
"images":
"http://www.qingcheng.com/image/1.jpg,http://www.qingcheng.com/image/2.jp
g",
"status": "1",
"weight": 130
},
{
"sn": "10192010293",
"num": 100,
"alertNum": 20,
"price": 600000,
"spec": {"颜色":"绿","机身内存":"8G"},
"image": "http://www.qingcheng.com/image/1.jpg",
"images":
        "http://www.qingcheng.com/image/1.jpg,http://www.qingcheng.com/image/2.jp
        g",
        "status": "1",
        "weight": 130
        }
        ]
        }

2.3 代码实现

既然前端已经将信息发送过来了,那么如何快速有效的接受信息?
创建一个数据对象吸收信息,变成后端可以处理的数据对象

2.3.1 SPU与SKU列表的保存

代码实现:
(1)qingcheng_pojo工程创建组合实体类

/**
* 商品组合实体类
*/
public class Goods implements Serializable {
private Spu spu;
private List<Sku> skuList;
public Spu getSpu() {
return spu;
}
public void setSpu(Spu spu) {
this.spu = spu;
}
public List<Sku> getSkuList() {
return skuList;
}
public void setSkuList(List<Sku> skuList) {
this.skuList = skuList;
}
}

(2)qingcheng_interface工程SpuService新增方法定义
在接口中新增方法

/**
* 保存商品
* @param goods 商品组合实体类
*/
public void saveGoods(Goods goods);

(3)qingcheng_service_goods工程SpuServiceImpl实现此方法
实现接口中新定义的方法
就像准备一个接收数据的罐子
数字类型加空字符转化为字符串类型?数据空间占用,否本来加的就是空,而非空格

sku名称在商品界面中没有,但是数据库中有,也需要操作
在这里插入图片描述

数据库中数据样式如下
sku名称 =spu名称+规格值列表
vivo Y81s 刘海全面屏 3GB+32GB 香槟金 移动联通电信4G手机
前面是spu名字,后面是sku参数
在这里插入图片描述
sku.getSpec()获得string字符串,在转化为json,
Map<String,String> specMap = JSON.parseObject(sku.getSpec(), Map.class);

这个方法要对sku和spu两个表进行操作,进行了多个操作
需要添加事务。@Transactional

@Autowired
private SkuMapper skuMapper;
@Autowired
private IdWorker idWorker;
@Autowired
private CategoryMapper categoryMapper;
/**
 * 保存商品
 * @param goods
 */
@Transactional
public void saveGoods(Goods goods) {
//保存一个spu的信息
        Spu spu = goods.getSpu();
        
        spu.setId(idWorker.nextId()+"");
        spuMapper.insert(spu);
//保存sku列表的信息
        Date date=new Date();
//分类对象
        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名称+规格值列表
        String name=spu.getName();
//sku.getSpec() {"颜色":"红","机身内存":"64G"}
        Map<String,String> specMap = JSON.parseObject(sku.getSpec(),
        Map.class);
        for(String value:specMap.values()){
        name+=" "+value;
        }
        sku.setName(name);//名称
        sku.setCreateTime(date);//创建日期
        sku.setUpdateTime(date);//修改日期
        sku.setCategoryId(spu.getCategory3Id());//分类id
        sku.setCategoryName(category.getName());//分类名称
        sku.setCommentNum(0);//评论数
        sku.setSaleNum(0);//销售数量
        skuMapper.insert(sku);
        }
        }

最后我们在类上添加@Transactional注解,并且在@Service注解中指定接口为
SpuService.class,否则这个类被多个接口所代理,不知道被谁代理,导致无法服务

@Service(interfaceClass=SpuService.class)

(3)qingcheng_web_manager工程SpuController修改add方法
既然功能模块已经完成, 写controller方法进行调用

@PostMapping("/save")
public Result save(@RequestBody Goods goods){
spuService.saveGoods(goods);
return new Result();
}

在没有前端页面的情形下如何测试模块是否可用呢?
使用postman方法可以模拟
在此之前,将修改后的公共模块重新安装
service加了雪花id生成模块
pojo加了goods实体类
interface
开启zk,goods,manger管理,
如图使用postman将案例json传送
在这里插入图片描述
数据库中
spu表格,打开过滤器检索,发现插入成功
在这里插入图片描述

2.3.2 建立分类与品牌的关联

需求:
(1)为什么要建立分类与品牌的关联?
因为我们在前台搜索时需要通过分类找到品牌列表。
(2)分类与品牌是什么关系?
多对多。
比如手机分类,有小米苹果品牌
然而小米品牌,也有电视,手机分类
那么需要为其添加额外的功能实现这种关系吗?
用户选择了手机,然后选择小米
用户选择了小米品牌,还要继续选择手机?不需要
因此这个功能不能单独实现

(3)在什么地方添加关系?
我们不在后台单独实现分类与品牌的关联,而是在添加商品时自动添加分类和品牌关联。在用户已有的操作中获得关联信息

实现思路:
(1)设计中间表tb_category_brand表
(2)根据表创建实体类、数据访问接口
(3)在添加商品的saveGoods方法中添加代码逻辑 ,将SPU的品牌编号和分类编号 一
起插入到(中间表)中。
代码实现:

代码生成器不会自动生成吗?
此表有两个主键,其对于有两个主键的表并不主动生成
(1)创建中间表的实体类

@Table(name="tb_category_brand")
public class CategoryBrand implements Serializable {
    @Id
    private Integer categoryId;
    @Id
    private Integer brandId;
    public Integer getCategoryId() {
        return categoryId;
    }
    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }
    public Integer getBrandId() {
        return brandId;
    }
    public void setBrandId(Integer brandId) {
        this.brandId = brandId;
    }
}

这个表是联合主键,所以templateId和brandId都有@Id注解
(2)新建数据访问接口

public interface CategoryBrandMapper extends Mapper<CategoryBrand> {
}

(3)修改saveGoods方法,添加以下代码
创建分类和品牌的关联
在这里插入图片描述
需要注意的是,既然是多对多,需要去重if(count==0) {

CategoryBrand categoryBrand =new CategoryBrand();
categoryBrand.setBrandId(spu.getBrandId());
categoryBrand.setCategoryId(spu.getCategory3Id());
int count=categoryBrandMapper.selectCount(categoryBrand);
if(count==0) {
categoryBrandMapper.insert(categoryBrand);
}

pojo创建了实体类,更新后需要重新安装
运行service,manger
插入数据可以看到绑定关系存在
在这里插入图片描述
数据库
在这里插入图片描述

2.3.3 根据ID查询商品

商品的新增与修改逻辑相同,根据流程重新走一遍即可
但是这里需要将待修改的数据加载出来
在这里插入图片描述

需求:根据id 查询SPU和SKU列表 ,显示效果如下:
查spu和sku的信息,返回给前端相同格式的数据即可
代码实现:
(1)qingcheng_interface工程SpuService新增方法定义

/**
* 根据ID查询商品
* @param id
* @return
*/
public Goods findGoodsById(String id);

(2)qingcheng_service_goods工程SpuServiceImpl实现此方法

/**
 * 根据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;
        }

(3)qingcheng_web_manager工程SpuController新增方法

@GetMapping("/findGoodsById")
public Goods findGoodsById(Long id){
return spuService.findGoodsById(id);
}

如图,已经根据id查找到数据库,并封装为json文件
在这里插入图片描述

2.3.4 保存修改

实现思路:
(1)修改与新增共用同一个方法
(2)通过spu的id判断当前操作是新增还是修改
(3)如果是新增需要设置spu的id,对spu执行的是insert操作
(4) 如果修改则需要删除原来的sku列表,对spu执行的是
updateByPrimaryKeySelective操作。
(5)sku列表的插入部分的代码要判断sku是否有id,如果有id则不重新生成id
代码实现:
修改SpuServiceImpl的saveGoods方法,修改后代码如下:

/**
* 保存商品
* @param goods
*/
@Transactional
public void saveGoods(Goods goods) {
//保存一个spu的信息
Spu spu = goods.getSpu();
if(spu.getId()==null){//新增商品
spu.setId(idWorker.nextId()+"");
spuMapper.insert(spu);
}else{ //修改
//删除原来的sku列表
Example example=new Example(Sku.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("spuId",spu.getId());
skuMapper.deleteByExample(example);
//执行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);//创建日期
}
//添加sku 。。。。。。(略)
}
//建立分类和品牌的关联
//。。。。。。(略)
}

2.3.5 未启用规格的sku处理

需求分析:
有些商品是没有区分规格的,也就是一个spu对应一个sku ,这种情况下sku列表只传递
一条记录,并且没有规格(spec)属性,我们要对其进行判断,避免因空值产生
做一个容错处理
实现思路:
在saveGoods方法的sku列表循环中添加代码,判断
商品没有spec参数自然不需要添加

//构建SKU名称,采用SPU+规格值组装
if(sku.getSpec()==null || "".equals(sku.getSpec())){
        sku.setSpec("{}");
        }

3. 商品审核与上下架

3.1 商品审核

有些商品是违法的,有些商品信息错误

3.1.1 需求分析与实现思路

商品审核:新录入的商品是未审核状态,也是未上架状态。
实现思路:
(1)修改审核状态,如果审核状态为1则上架状态也修改为1
(2)记录商品审核记录,审核记录表
(3)记录商品日志,商品日志表

3.1.2 代码实现

(1)SpuService新增方法定义

public void audit(String id,String status,String message);

(2)SpuServiceImpl实现方法

/**
 * 商品审核
 * @param id
 * @param status
 * @param message
 */
@Transactional
public void audit(String id, String status, String message) {
//1.修改状态 审核状态和上架状态
        Spu spu = new Spu();
        spu.setId(id);
        spu.setStatus(status);
        if("1".equals(status)){//审核通过
        spu.setIsMarketable("1");//自动上架
        }
        spuMapper.updateByPrimaryKeySelective(spu);
//2.记录商品审核记录
//3.记录商品日志
        }

(3)SpuController新增方法

@GetMapping("/audit")
public Result audit(Long id){
        spuService.audit(id);
        return new Result();
        }

3.2 下架商品

3.2.1 需求与实现思路

下架商品,修改上下架状态为下架。下架商品不修改审核状态。
下架商品需要记录商品日志。

3.2.2 代码实现

(1)SpuService新增方法定义

/**
 * 下架商品
 * @param id
 */
public void pull(String id);

(2)SpuServiceImpl实现方法

/**
* 下架商品
* @param id
*/
public void pull(String id) {
Spu spu = spuMapper.selectByPrimaryKey(id);
spu.setIsMarketable("0");//下架状态
spuMapper.updateByPrimaryKeySelective(spu);
}

(3)SpuController新增方法

@GetMapping("/pull")
public Result pull(String id){
        spuService.pull(id);
        return new Result();
        }

3.3 上架商品

3.3.1 需求分析

将商品修改为上架状态,需要验证该商品是否审核通过,未审核通过的商品不能上架。
上架商品需要记录商品日志。

3.3.2 代码实现

必须是通过审核的商品才能上架
(1)SpuService新增方法定义

/**
 * 上架商品
 * @param id
 */
public void put(String id);

(2)SpuServiceImpl实现方法

/**
 * 商品上架
 * @param id
 */
public void put(String id) {
//1.修改状态
        Spu spu = spuMapper.selectByPrimaryKey(id);
        if(!"1".equals(spu.getStatus())){
        throw new RuntimeException("此商品未通过审核");
        }
        spu.setIsMarketable("1");
        spuMapper.updateByPrimaryKeySelective(spu);
//2.记录商品日志
。。。
        }

(3)SpuController新增方法

@GetMapping("/put")
public Result put(String id){
spuService.put(id);
return new Result();
}

3.4 批量上下架

3.4.1 需求分析

前端传递一组商品ID,后端进行批量上下架处理,处理后给前端返回处理的条数

3.4.2 代码实现

(1)SpuService新增方法定义

/**
 * 批量上架商品
 * @param ids
 */
public int putMany(Long[] ids);

(2)SpuServiceImpl实现方法

/**
 * 批量上架商品
 * @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");//审核通过的
        criteria.andEqualTo("isDelete","0");//非删除的
        return spuMapper.updateByExampleSelective(spu, example);
        }

(3)SpuController新增方法

@GetMapping("/putMany")
public Result putMany(Long[] ids){
        int count = spuService.putMany(ids);
        return new Result(0,"上架"+count+"个商品");
        }

批量下架代码略

4. 删除与还原商品

4.1 需求分析

删除商品并非物理删除(真正的执行删除数据),而是通过将表中某字段标记为删除状
态。
还原商品实际就是将删除状态再修改回来。
如果商品需要物理删除,必须是先逻辑删除才能进行物理删除,删除前需要检查状态。

4.2 实现思路

逻辑删除商品,修改spu表is_delete字段为1
商品回收站显示spu表is_delete字段为1的记录
回收商品,修改spu表is_delete字段为0

数据库操作略

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值