尚品汇商城

一、spu相关业务介绍

1.1 销售属性

销售属性,就是商品详情页右边,可以通过销售属性来定位一组spu下的哪款sku。可以让当前的商品详情页,跳转到自己的“兄弟”商品。
一般每种商品的销售属性不会太多,大约1-4种。整个电商的销售属性种类也不会太多,大概10种以内。比如:颜色、尺寸、版本、套装等等。不同销售属性的组合也就构成了一个spu下多个sku的结构。
04 商品spu保存 - 图1

因此,在制作spu之前要先确定当前商品有哪些销售属性!

1.2 spu数据结构图

04 商品spu保存 - 图2

二、列表查询功能开发

2.1 创建mapper

@Mapper
public interface SpuInfoMapper extends BaseMapper {

}

2.2 创建接口ManageService

/
spu分页查询
@param **
pageParam
__
*@param spuInfo
__
_ @return
**
**
/
_IPage getSpuInfoPage(Page pageParam, SpuInfo spuInfo);

2.3 创建实现类 ManageServiceImpl

@Autowired
private SpuInfoMapper spuInfoMapper;

@Overridepublic IPage getSpuInfoPage(Page pageParam, SpuInfo spuInfo) {
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.eq(“category3_id”, spuInfo.getCategory3Id());
queryWrapper.orderByDesc(“id”);
return spuInfoMapper.selectPage(pageParam, queryWrapper);
}

2.4 创建控制器SpuManageController

@RestController // @ResponseBody + @Controller
@RequestMapping(“admin/product”)
public class SpuManageController {

@Autowired
private ManageService manageService;

// 根据查询条件封装控制器
// springMVC 的时候,有个叫对象属性传值 如果页面提交过来的参数与实体类的参数一致,
// 则可以使用实体类来接收数据
//
http://api.gmall.com/admin/product/1/10?category3Id=61
_ // @RequestBody 作用 将前台传递过来的json{“category3Id”:”61”} 字符串变为java 对象。
@GetMapping(“{page}/{size}”)
public Result getSpuInfoPage(@PathVariable Long page,
@PathVariable Long size,
SpuInfo spuInfo){
// 创建一个Page 对象
_Page spuInfoPage = new Page<>(page,size);
// 获取数据
IPage spuInfoPageList = manageService.getSpuInfoPage(spuInfoPage, spuInfo);
// 将获取到的数据返回即可!
return Result._ok(spuInfoPageList);
}
}

三、品牌管理

3.1 品牌列表

3.1.1 创建mapper

@Mapper
public interface BaseTrademarkMapper extends BaseMapper {

}



3.1.2 创建接口BaseTrademarkService

public interface BaseTrademarkService extends IService {

/
Banner分页列表
@param **
pageParam
__
_ @return
**
**
/
_IPage getPage(Page pageParam);

}

3.1.3 创建实现类BaseTrademarkServiceImpl

| @Servicepublic class BaseTrademarkServiceImpl extends ServiceImpl implements BaseTrademarkService {

@Autowired
private BaseTrademarkMapper baseTrademarkMapper;

@Override
public IPage getPage(Page pageParam) {
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.orderByAsc(“id”);

  1. IPage<BaseTrademark> page = **baseTrademarkMapper**.selectPage(pageParam, queryWrapper);<br /> **return **page;<br /> }<br />} |

| —- |

3.1.4 创建控制器 BaseTrademarkController

@RestController
@RequestMapping(“/admin/product/baseTrademark”)
public class BaseTrademarkController {

@Autowired
private BaseTrademarkService baseTrademarkService;

@ApiOperation(value = “分页列表”)
@GetMapping(“{page}/{limit}”)
public Result index(@PathVariable Long page,
@PathVariable Long limit) {

Page pageParam = new Page<>(page, limit);
IPage pageModel = baseTrademarkService.getPage(pageParam);
return Result.ok(pageModel);
}

@ApiOperation(value = “获取BaseTrademark”)
@GetMapping(“get/{id}”)
public Result get(@PathVariable String id) {
BaseTrademark baseTrademark = baseTrademarkService.getById(id);
return Result.ok(baseTrademark);
}

@ApiOperation(value = “新增BaseTrademark”)
@PostMapping(“save”)
public Result save(@RequestBody BaseTrademark banner) {
baseTrademarkService.save(banner);
return Result.ok();
}

@ApiOperation(value = “修改BaseTrademark”)
@PutMapping(“update”)
public Result updateById(@RequestBody BaseTrademark banner) {
baseTrademarkService.updateById(banner);
return Result.ok();
}

@ApiOperation(value = “删除BaseTrademark”)
@DeleteMapping(“remove/{id}”)
public Result remove(@PathVariable Long id) {
baseTrademarkService.removeById(id);
return Result.ok();
}

}

3.2 分类品牌列表

3.2.1 创建mapper

@Mapper
public interface BaseCategoryTrademarkMapper extends BaseMapper {
}



3.2.2 创建接口

package com.atguigu.gmall.product.service;
public interface BaseCategoryTrademarkService extends IService {

/
根据三级分类获取品牌
@param **
category3Id
__
@return
**
**
/
_List findTrademarkList(Long category3Id);

/
保存分类与品牌关联
@param _categoryTrademarkVo
__
*/
void save(CategoryTrademarkVo categoryTrademarkVo);

_/

获取当前未被三级分类关联的所有品牌 @param **category3Id
_
_* @return
* /
List findCurrentTrademarkList(Long category3Id);

/
删除关联
@param _category3Id
__
_* @param _trademarkId
__
*/
void **remove(Long category3Id, Long trademarkId);
}

3.2.3 创建实现类

package com.atguigu.gmall.product.service.impl; @Service
public class BaseCategoryTrademarkServiceImpl extends ServiceImpl implements BaseCategoryTrademarkService {

// 调用mapper 层!
@Autowired
private BaseTrademarkMapper baseTrademarkMapper;

@Autowired
private BaseCategoryTrademarkMapper baseCategoryTrademarkMapper;

@Override
public List findTrademarkList(Long category3Id) {
// 根据分类Id 获取到品牌Id 集合数据
// select from base_category_trademark where category3_id = ?;
_QueryWrapper baseCategoryTrademarkQueryWrapper = new QueryWrapper<>();
baseCategoryTrademarkQueryWrapper.eq(“category3_id”,category3Id);
List baseCategoryTrademarkList = *baseCategoryTrademarkMapper
.selectList(baseCategoryTrademarkQueryWrapper);

// 判断baseCategoryTrademarkList 这个集合
if(!CollectionUtils._isEmpty(baseCategoryTrademarkList)){
// 需要获取到这个集合中的品牌Id 集合数据
_List tradeMarkIdList = baseCategoryTrademarkList.stream().map(baseCategoryTrademark -> {
return baseCategoryTrademark.getTrademarkId();
}).collect(Collectors._toList
());
// 正常查询数据的话… 需要根据品牌Id 来获取集合数据!
return baseTrademarkMapper.selectBatchIds(tradeMarkIdList);
}
// 如果集合为空,则默认返回空
return null;
}

@Override
public void removeBaseCategoryTrademarkById(Long category3Id, Long trademarkId) {
// 逻辑删除: 本质更新操作 is_deleted
// 更新: update base_category_trademark set is_deleted = 1 where category3_id=? and trademark_id=?;
_QueryWrapper baseCategoryTrademarkQueryWrapper = new QueryWrapper<>();
baseCategoryTrademarkQueryWrapper.eq(“category3_id”,category3Id);
baseCategoryTrademarkQueryWrapper.eq(“trademark_id”,trademarkId);
baseCategoryTrademarkMapper.delete(baseCategoryTrademarkQueryWrapper);

}

@Override
public List findCurrentTrademarkList(Long category3Id) {
// 哪些是关联的品牌Id
QueryWrapper baseCategoryTrademarkQueryWrapper = new QueryWrapper<>();
baseCategoryTrademarkQueryWrapper.eq(“category3_id”,category3Id);
List baseCategoryTrademarkList = baseCategoryTrademarkMapper.selectList(baseCategoryTrademarkQueryWrapper);

// 判断
if (!CollectionUtils._isEmpty(baseCategoryTrademarkList)){
// 找到关联的品牌Id 集合数据 {1,3}
_List tradeMarkIdList = baseCategoryTrademarkList.stream().map(baseCategoryTrademark -> {
return baseCategoryTrademark.getTrademarkId();
}).collect(Collectors._toList
());
// 在所有的品牌Id 中将这些有关联的品牌Id 给过滤掉就可以!
// select from base_trademark; 外面 baseTrademarkMapper.selectList(null) {1,2,3,5}
_List baseTrademarkList = baseTrademarkMapper.selectList(null).stream().filter(baseTrademark -> {
*return
!tradeMarkIdList.contains(baseTrademark.getId());
}).collect(Collectors._toList
());
// 返回数据
return baseTrademarkList;
}
// 如果说这个三级分类Id 下 没有任何品牌! 则获取到所有的品牌数据!
return baseTrademarkMapper.selectList(null);
}

@Override
public void save(CategoryTrademarkVo categoryTrademarkVo) {
/
private Long category3Id;
private List trademarkIdList;

category3Id 61 tmId 2;
category3Id 61 tmId 5;
/
// 获取到品牌Id 集合数据
_List trademarkIdList = categoryTrademarkVo.getTrademarkIdList();


// 判断
if (!CollectionUtils._isEmpty(trademarkIdList)){
// 做映射关系
_List baseCategoryTrademarkList = trademarkIdList.stream().map((trademarkId) -> {
// 创建一个分类Id 与品牌的关联的对象
BaseCategoryTrademark baseCategoryTrademark = new BaseCategoryTrademark();
baseCategoryTrademark.setCategory3Id(categoryTrademarkVo.getCategory3Id());
baseCategoryTrademark.setTrademarkId(trademarkId);
// 返回数据
return baseCategoryTrademark;
}).collect(Collectors._toList
());

// 集中保存到数据库 baseCategoryTrademarkList
this.saveBatch(baseCategoryTrademarkList);
}
}
}

3.2.4 创建控制器

package com.atguigu.gmall.product.controller;
@RestController
@RequestMapping(“admin/product/baseCategoryTrademark”)
public class BaseCategoryTrademarkController {

@Autowired
private BaseCategoryTrademarkService baseCategoryTrademarkService;


@PostMapping(“save”)
public Result save(@RequestBody CategoryTrademarkVo categoryTrademarkVo){
// 保存方法
baseCategoryTrademarkService.save(categoryTrademarkVo);
return Result.ok();
}

@DeleteMapping(“remove/{category3Id}/{trademarkId}”)
public Result remove(@PathVariable Long category3Id, @PathVariable Long trademarkId){
// 调用服务层方法
baseCategoryTrademarkService.remove(category3Id, trademarkId);
return Result.ok();
}

@GetMapping(“findTrademarkList/{category3Id}”)
public Result findTrademarkList(@PathVariable Long category3Id){
// select from base_trademark
_List list = *baseCategoryTrademarkService
.findTrademarkList(category3Id);
// 返回
return Result._ok(list);
}

@GetMapping(“findCurrentTrademarkList/{category3Id}”)
public Result findCurrentTrademarkList(@PathVariable Long category3Id){
List list = baseCategoryTrademarkService.findCurrentTrademarkList(category3Id);
// 返回
return Result.ok(list);
}

}

四、spu的保存功能中的图片上传

4.1 MinIo介绍

MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。
官方文档:http://docs.minio.org.cn/docs

4.2 应用场景

4.2.1单主机单硬盘模式

04 商品spu保存 - 图3

4.2.2单主机多硬盘模式

04 商品spu保存 - 图4

4.2.3多主机多硬盘分布式

04 商品spu保存 - 图5

4.3 特点

· 高性能:作为高性能对象存储,在标准硬件条件下它能达到55GB/s的读、35GG/s的写速率
· 可扩容:不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并跨越多个数据中心
· 云原生:容器化、基于K8S的编排、多租户支持
· Amazon S3兼容:Minio使用Amazon S3 v2 / v4 API。可以使用Minio SDK,Minio Client,AWS SDK和AWS CLI访问Minio服务器。
· 可对接后端存储: 除了Minio自己的文件系统,还支持DAS、 JBODs、NAS、Google云存储和Azure Blob存储。
· SDK支持: 基于Minio轻量的特点,它得到类似Java、Python或Go等语言的sdk支持
· Lambda计算: Minio服务器通过其兼容AWS SNS / SQS的事件通知服务触发Lambda功能。支持的目标是消息队列,如Kafka,NATS,AMQP,MQTT,Webhooks以及Elasticsearch,Redis,Postgres和MySQL等数据库。
· 有操作页面
· 功能简单: 这一设计原则让MinIO不容易出错、更快启动
· 支持纠删码:MinIO使用纠删码、Checksum来防止硬件错误和静默数据污染。在最高冗余度配置下,即使丢失1/2的磁盘也能恢复数据!

4.4 存储机制

Minio使用纠删码erasure code和校验和checksum。 即便丢失一半数量(N/2)的硬盘,仍然可以恢复数据。
纠删码是一种恢复丢失和损坏数据的数学算法。

4.5 docker安装Minio

docker pull minio/minio
新版本:
docker run \
-p 9000:9000 \
-p 9001:9001 \
—name minio \
-d —restart=always \
-e “MINIO_ROOT_USER=admin“ \
-e “MINIO_ROOT_PASSWORD=admin123456“ \
-v /home/data:/data \
-v /home/config:/root/.minio \
minio/minio server /data —console-address “:9001”

浏览器访问:http://IP:9000/minio/login,如图:
04 商品spu保存 - 图6
说明:安装是指定了登录账号

4.6 利用Java客户端调用Minio

参考文档:https://docs.min.io/docs/java-client-api-reference.html

4.6.1 引入依赖

在service-product模块中添加依赖

<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.2.0</version> </dependency>

4.6.2 添加配置文件

在nacos 中配置好了!

minio:
endpointUrl: http://IP:9000
accessKey: admin
secreKey: admin123456
bucketName: gmall

4.6.3 创建FileUploadController控制器

package com.atguigu.gmall.product.controller;
@RestController
@RequestMapping(“admin/product”)
public class FileUploadController {

// 获取文件上传对应的地址 @Value(“${minio.endpointUrl}”)
public String endpointUrl;

@Value(“${minio.accessKey}”)
public String accessKey;

@Value(“${minio.secreKey}”)
public String secreKey;

@Value(“${minio.bucketName}”)
public String bucketName;

// 文件上传控制器 @PostMapping(“fileUpload”)
public Result fileUpload(MultipartFile file) throws Exception{
// 准备获取到上传的文件路径! _String url = “”;

// 使用MinIO服务的URL,端口,Access key和Secret key创建一个MinioClient对象 // MinioClient minioClient = new MinioClient(“https://play.min.io“, “Q3AM3UQ867SPQQA43P2F”, “zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG”);
MinioClient minioClient =
MinioClient._builder
()
.endpoint(endpointUrl)
.credentials(accessKey, secreKey)
.build();
// 检查存储桶是否已经存在 boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if(isExist) {
System.out.println(“Bucket already exists.”);
} else {
// 创建一个名为asiatrip的存储桶,用于存储照片的zip文件。 _minioClient.makeBucket(MakeBucketArgs._builder()
.bucket(bucketName)
.build());
}
// 定义一个文件的名称 : 文件上传的时候,名称不能重复! _String fileName = System._currentTimeMillis()+ UUID.randomUUID().toString();
// 使用putObject上传一个文件到存储桶中。 // minioClient.putObject(“asiatrip”,”asiaphotos.zip”, “/home/user/Photos/asiaphotos.zip”);
_minioClient.putObject(
PutObjectArgs._builder
().bucket(bucketName).object(fileName).stream(
file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
// System.out.println(“/home/user/Photos/asiaphotos.zip is successfully uploaded as asiaphotos.zip to asiatrip bucket.”);
// 文件上传之后的路径:
http://39.99.159.121:9000/gmall/xxxxxx
_ _url = endpointUrl+“/“+bucketName+“/“+fileName;

System.**_out
.println(“url:\t”+url);
// 将文件上传之后的路径返回给页面!
return **Result.ok(url);
}
}
注意:文件上传时,需要调整一下linux 服务器的时间与windows 时间一致!第一步:安装ntp服务yum -y install ntp 第二步:开启开机启动服务systemctl enable ntpd 第三步:启动服务systemctl start ntpd 第四步:更改时区timedatectl set-timezone Asia/Shanghai 第五步:启用ntp同步timedatectl set-ntp yes 第六步:同步时间ntpq -p

五、spu保存

5.1 加载销售属性

5.1.1 创建mapper

@Mapper
public interface BaseSaleAttrMapper extends BaseMapper {
}

5.1.2 在MangeService添加接口

接口
_/
查询所有的销售属性数据
@return
* /
_List getBaseSaleAttrList();
实现类
@Autowired
private BaseSaleAttrMapper baseSaleAttrMapper;

@Override
public List getBaseSaleAttrList() {
return baseSaleAttrMapper.selectList(null);
}

5.1.4 添加控制器SpuManageController

@RestController
@RequestMapping(“admin/product”)
public class SpuManageController {

// 引入服务层 @Autowired
private ManageService manageService;

// 销售属性http://api.gmall.com/admin/product/baseSaleAttrList
_ @GetMapping(“baseSaleAttrList”)
public Result baseSaleAttrList(){
// 查询所有的销售属性集合 _List baseSaleAttrList = manageService.getBaseSaleAttrList();

return Result._ok
(baseSaleAttrList);
}
}

5.2 加载品牌数据

BaseCategoryTrademarkController/
查询全部品牌
@return
* /
@GetMapping(“findTrademarkList/{category3Id}”)
public Result findTrademarkList(@PathVariable Long category3Id){
// 调用服务层方法 _List baseCategoryTrademarkList = baseCategoryTrademarkService.findTrademarkList(category3Id);
// 返回数据 return Result._ok(baseCategoryTrademarkList);
}

5.3 保存后台代码

5.3.1 创建mapper

建立对应的mapper 文件

@Mapper
public interface SpuImageMapper extends BaseMapper {
}@Mapper
public interface SpuSaleAttrMapper extends BaseMapper {
}@Mapper
public interface SpuSaleAttrValueMapper extends BaseMapper {
}@Mapper
public interface SpuPosterMapper extends BaseMapper {
}

5.3.2 添加数据接口

/
保存商品数据
@param **
spuInfo
__
*/
void saveSpuInfo(SpuInfo spuInfo);
@Override
@Transactional(rollbackFor = Exception.class)
public void saveSpuInfo(SpuInfo spuInfo) {
/
spuInfo;
spuImage;
spuSaleAttr;
spuSaleAttrValue;
spuPoster
/
spuInfoMapper.insert(spuInfo);

// 获取到spuImage 集合数据
_List spuImageList = spuInfo.getSpuImageList();
// 判断不为空
if (!CollectionUtils._isEmpty(spuImageList)){
// 循环遍历
for (SpuImage spuImage : spuImageList) {
// 需要将spuId 赋值
_spuImage.setSpuId(spuInfo.getId());
// 保存spuImge
spuImageMapper.insert(spuImage);
}
}
// 获取销售属性集合
List spuSaleAttrList = spuInfo.getSpuSaleAttrList();
// 判断
if (!CollectionUtils._isEmpty(spuSaleAttrList)){
// 循环遍历
for (SpuSaleAttr spuSaleAttr : spuSaleAttrList) {
// 需要将spuId 赋值
_spuSaleAttr.setSpuId(spuInfo.getId());
spuSaleAttrMapper.insert(spuSaleAttr);

// 再此获取销售属性值集合
List spuSaleAttrValueList = spuSaleAttr.getSpuSaleAttrValueList();
// 判断
if (!CollectionUtils._isEmpty(spuSaleAttrValueList)){
// 循环遍历
for (SpuSaleAttrValue spuSaleAttrValue : spuSaleAttrValueList) {
// 需要将spuId, sale_attr_name 赋值
_spuSaleAttrValue.setSpuId(spuInfo.getId());
spuSaleAttrValue.setSaleAttrName(spuSaleAttr.getSaleAttrName());
spuSaleAttrValueMapper.insert(spuSaleAttrValue);
}
}
}
}

// 获取到posterList 集合数据
List spuPosterList = spuInfo.getSpuPosterList();
// 判断不为空
if (!CollectionUtils._isEmpty(spuPosterList)){
for (SpuPoster spuPoster : spuPosterList) {
// 需要将spuId 赋值
_spuPoster.setSpuId(spuInfo.getId());
// 保存spuPoster
_spuPosterMapper.insert(spuPoster);
}
}
}

5.3.3 添加控制器

/
保存spu
@param **
spuInfo
__
@return
**
**
/
@PostMapping(“saveSpuInfo”)public Result saveSpuInfo(@RequestBody SpuInfo spuInfo){
// 调用服务层的保存方法 manageService.saveSpuInfo(spuInfo);
return Result.ok();
}