## 第3章-商品SPU管理 **学习目标:** - 完成品牌管理 - 了解SPU商品相关的概念 - 完成SPU列表功能 - 掌握MinIO分布式文件存储服务 - 完成商品SPU保存功能 # 1. 品牌管理 ![image-20221212234420609](assets/image-20221212234420609.png) ## 1.1 生成基础代码 在代码生成器中,生成品牌相关的表到`service-product`商品微服务模块,相关表名称如下 - base_trademark 品牌表 - base_category_trademark 分类品牌中间表 ![image-20221212171431019](assets/image-20221212171431019.png) ## 1.2 品牌分页查询 需求: ![image-20221212204805066](assets/image-20221212204805066.png) > YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/371 ### 1.2.1 控制器BaseTrademarkController ```java package com.atguigu.gmall.product.controller; import com.atguigu.gmall.common.result.Result; import com.atguigu.gmall.product.model.BaseTrademark; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.annotations.Api; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import com.atguigu.gmall.product.service.BaseTrademarkService; import org.springframework.web.bind.annotation.RestController; /** * 品牌表 前端控制器 * * @author atguigu * @since 2022-12-24 */ @Api(tags = "品牌表控制器") @RestController @RequestMapping("/admin/product/baseTrademark") public class BaseTrademarkController { @Autowired private BaseTrademarkService baseTrademarkService; /** * 品牌分页查询 * @param page 页码 * @param limit 页大小 * @return */ @GetMapping("/{page}/{limit}") public Result> baseTrademarkByPage( @PathVariable("page") Long page, @PathVariable("limit") Long limit) { //1.封装分页请求参数Page - 分页对象只有页大小跟页码 IPage iPage = new Page<>(page, limit); //2.调用业务逻辑实现分页查询 执行完业务分页后 数据集合,总记录数等属性有值 iPage = baseTrademarkService.baseTrademarkByPage(iPage); return Result.ok(iPage); } } ``` ### 1.2.2 业务接口BaseTrademarkService ```java package com.atguigu.gmall.product.service; import com.atguigu.gmall.product.model.BaseTrademark; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; /** * 品牌表 业务接口类 * @author atguigu * @since 2022-12-24 */ public interface BaseTrademarkService extends IService { /** * 品牌分页查询 * @param iPage * @return */ IPage baseTrademarkByPage(IPage iPage); } ``` ### 1.2.3 业务实现类BaseTrademarkServiceImpl ```java package com.atguigu.gmall.product.service.impl; import com.atguigu.gmall.base.model.BaseEntity; import com.atguigu.gmall.product.model.BaseTrademark; import com.atguigu.gmall.product.mapper.BaseTrademarkMapper; import com.atguigu.gmall.product.service.BaseTrademarkService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; /** * 品牌表 业务实现类 * * @author atguigu * @since 2022-12-24 */ @Service public class BaseTrademarkServiceImpl extends ServiceImpl implements BaseTrademarkService { /** * 品牌分页查询 * * @param iPage 分页对象 * @return */ @Override public IPage baseTrademarkByPage(IPage iPage) { //1.page方法 第一个参数是分页对象, 第二个参数 分页查询条件 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByDesc(BaseEntity::getUpdateTime); //调用分页方法 iPage = super.page(iPage, queryWrapper); return iPage; } } ``` ### 1.2.4 持久层mapper ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.BaseTrademark; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface BaseTrademarkMapper extends BaseMapper { } ``` ### 1.2.5 其他业务代码 BaseTrademarkController 中完成品牌的增删改查,调用公共业务接口方法即可。 ```java package com.atguigu.gmall.product.controller; import com.atguigu.gmall.common.result.Result; import com.atguigu.gmall.product.model.BaseTrademark; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.annotations.Api; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import com.atguigu.gmall.product.service.BaseTrademarkService; import org.springframework.web.bind.annotation.RestController; /** * 品牌表 前端控制器 * * @author atguigu * @since 2022-12-24 */ @Api(tags = "品牌表控制器") @RestController @RequestMapping("/admin/product/baseTrademark") public class BaseTrademarkController { @Autowired private BaseTrademarkService baseTrademarkService; /** * 根据品牌ID 查询品牌对象 * * @param id 品牌ID * @return */ @GetMapping("/get/{id}") public Result getById(@PathVariable("id") Long id) { BaseTrademark baseTrademark = baseTrademarkService.getById(id); return Result.ok(baseTrademark); } /** * 修改品牌信息 * @param baseTrademark * @return */ @PutMapping("/update") public Result updateById(@RequestBody BaseTrademark baseTrademark) { baseTrademarkService.updateById(baseTrademark); return Result.ok(); } /** * 品牌删除 * @param id 品牌ID * @return */ @DeleteMapping("/remove/{id}") public Result deleteById(@PathVariable("id") Long id){ baseTrademarkService.removeById(id); return Result.ok(); } /** * 品牌保存 * @param baseTrademark * @return */ @PostMapping("/save") public Result saveTrademark(@RequestBody BaseTrademark baseTrademark){ baseTrademarkService.save(baseTrademark); return Result.ok(); } } ``` ## 2.2 分类品牌管理 > YAPI接口地址: > > - 根据category3Id获取品牌列表:http://192.168.200.128:3000/project/11/interface/api/171 > - 根据category3Id获取可选品牌列表:http://192.168.200.128:3000/project/11/interface/api/163 > > - 新增:http://192.168.200.128:3000/project/11/interface/api/147 > - 删除:http://192.168.200.128:3000/project/11/interface/api/155 ### 1.2.1 创建控制器 ```java package com.atguigu.gmall.product.controller; import com.atguigu.gmall.common.result.Result; import com.atguigu.gmall.product.model.BaseTrademark; import com.atguigu.gmall.product.model.CategoryTrademarkVo; import io.swagger.annotations.Api; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import com.atguigu.gmall.product.service.BaseCategoryTrademarkService; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * 分类品牌中间表 前端控制器 * * @author atguigu * @since 2022-12-24 */ @Api(tags = "分类品牌中间表控制器") @RestController @RequestMapping("/admin/product/baseCategoryTrademark") public class BaseCategoryTrademarkController { @Autowired private BaseCategoryTrademarkService baseCategoryTrademarkService; /** * 查询分类下关联的品牌列表 * * @param category3Id 三级分类ID * @return */ @GetMapping("/findTrademarkList/{category3Id}") public Result> findTrademarkList(@PathVariable("category3Id") Long category3Id) { List list = baseCategoryTrademarkService.findTrademarkList(category3Id); return Result.ok(list); } /** * 查询当前分类可选品牌列表 * @param category3Id 三级分类ID * @return */ @GetMapping("/findCurrentTrademarkList/{category3Id}") public Result> findCurrentTrademarkList(@PathVariable("category3Id") Long category3Id){ List list = baseCategoryTrademarkService.findCurrentTrademarkList(category3Id); return Result.ok(list); } /** * 将品牌关联到分类 * @param categoryTrademarkVo * @return */ @PostMapping("/save") public Result saveBasecategoryTrademark(@RequestBody CategoryTrademarkVo categoryTrademarkVo){ baseCategoryTrademarkService.saveBasecategoryTrademark(categoryTrademarkVo); return Result.ok(); } /** * 删除分类品牌关联 * @param category3Id * @param trademarkId * @return */ @DeleteMapping("/remove/{category3Id}/{trademarkId}") public Result removeCategoryTrademark(@PathVariable("category3Id") Long category3Id, @PathVariable("trademarkId") Long trademarkId){ baseCategoryTrademarkService.removeCategoryTrademark(category3Id, trademarkId); return Result.ok(); } } ``` ### 3.2.2 业务接口 ```java package com.atguigu.gmall.product.service; import com.atguigu.gmall.product.model.BaseCategoryTrademark; import com.atguigu.gmall.product.model.BaseTrademark; import com.atguigu.gmall.product.model.CategoryTrademarkVo; import com.baomidou.mybatisplus.extension.service.IService; import java.util.List; /** * 分类品牌中间表 业务接口类 * @author atguigu * @since 2022-12-24 */ public interface BaseCategoryTrademarkService extends IService { /** * 查询分类下关联的品牌列表 * * @param category3Id 三级分类ID * @return */ List findTrademarkList(Long category3Id); /** * 查询当前分类可选品牌列表 * @param category3Id 三级分类ID * @return */ List findCurrentTrademarkList(Long category3Id); /** * 将品牌关联到分类 * @param categoryTrademarkVo * @return */ void saveBasecategoryTrademark(CategoryTrademarkVo categoryTrademarkVo); /** * 删除分类品牌关联 * @param category3Id * @param trademarkId */ void removeCategoryTrademark(Long category3Id, Long trademarkId); } ``` ### 3.2.3 业务实现类 ```java package com.atguigu.gmall.product.service.impl; import com.atguigu.gmall.product.mapper.BaseTrademarkMapper; import com.atguigu.gmall.product.model.BaseCategoryTrademark; import com.atguigu.gmall.product.mapper.BaseCategoryTrademarkMapper; import com.atguigu.gmall.product.model.BaseTrademark; import com.atguigu.gmall.product.model.CategoryTrademarkVo; import com.atguigu.gmall.product.service.BaseCategoryTrademarkService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * 分类品牌中间表 业务实现类 * * @author atguigu * @since 2022-12-24 */ @Service public class BaseCategoryTrademarkServiceImpl extends ServiceImpl implements BaseCategoryTrademarkService { @Autowired private BaseTrademarkMapper baseTrademarkMapper; /** * 查询分类下关联的品牌列表 * * @param category3Id 三级分类ID * @return */ @Override public List findTrademarkList(Long category3Id) { //1.根据分类ID查询 分类品牌关系表得到分类品牌集合 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(BaseCategoryTrademark::getCategory3Id, category3Id); List baseCategoryTrademarks = super.list(queryWrapper); //2.获取分类品牌集合中品牌ID集合 if (!CollectionUtils.isEmpty(baseCategoryTrademarks)) { //采用Stream流处理集合 将集合对象中 品牌ID 获取 转为品牌ID集合 List trademardIdList = baseCategoryTrademarks.stream().map(BaseCategoryTrademark::getTrademarkId).collect(Collectors.toList()); //3.根据品牌ID集合查询品牌列表 List baseTrademarkList = baseTrademarkMapper.selectBatchIds(trademardIdList); return baseTrademarkList; } return null; } /** * 查询当前分类可选品牌列表 * * @param category3Id 三级分类ID * @return */ @Override public List findCurrentTrademarkList(Long category3Id) { //调用持久层方法执行自定义SQL得到可选品牌列表 List list = baseTrademarkMapper.findCurrentTrademarkList(category3Id); return list; } /** * 将品牌关联到分类 * * @param categoryTrademarkVo * @return */ @Override public void saveBasecategoryTrademark(CategoryTrademarkVo categoryTrademarkVo) { //1.获取品牌ID集合 List trademarkIdList = categoryTrademarkVo.getTrademarkIdList(); //2.遍历品牌ID集合构建品牌分类中间对象 执行新增 if (!CollectionUtils.isEmpty(trademarkIdList)) { //采用Stream流将集合泛型 由 Long 转为 BaseCategoryTrademark List baseCategoryTrademarkList = trademarkIdList.stream().map(tmId -> { BaseCategoryTrademark baseCategoryTrademark = new BaseCategoryTrademark(); baseCategoryTrademark.setTrademarkId(tmId); baseCategoryTrademark.setCategory3Id(categoryTrademarkVo.getCategory3Id()); return baseCategoryTrademark; }).collect(Collectors.toList()); //执行当前关系业务父类中方法 批量保存 super.saveBatch(baseCategoryTrademarkList); } } /** * 删除分类品牌关联 * * @param category3Id * @param trademarkId */ @Override public void removeCategoryTrademark(Long category3Id, Long trademarkId) { //方式一:逻辑删除 //LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); //queryWrapper.eq(BaseCategoryTrademark::getCategory3Id, category3Id); //queryWrapper.eq(BaseCategoryTrademark::getTrademarkId, trademarkId); //super.remove(queryWrapper); //方式二:执行物理删除 baseMapper.removeCategoryTrademark(category3Id, trademarkId); } } ``` ### 3.2.4 Mapper ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.BaseTrademark; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import java.util.List; /** * 品牌表 Mapper 接口 * * @author atguigu * @since 2022-12-24 */ public interface BaseTrademarkMapper extends BaseMapper { List findCurrentTrademarkList(@Param("category3Id") Long category3Id); } ``` ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.BaseCategoryTrademark; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import org.springframework.web.bind.annotation.PathVariable; /** * 分类品牌中间表 Mapper 接口 * * @author atguigu * @since 2022-12-24 */ public interface BaseCategoryTrademarkMapper extends BaseMapper { @Delete("delete bct from base_category_trademark bct where bct.category3_id = #{category3Id} and bct.trademark_id = #{trademarkId}") void removeCategoryTrademark(@Param("category3Id") Long category3Id, @PathVariable("trademarkId") Long trademarkId); } ``` **自定义SQL** ```xml ``` # 2. spu相关业务介绍 ## 2.1 销售属性 销售属性,就是商品详情页右边,可以通过销售属性来定位一组spu下的哪款sku。 ![img](assets/day03/wps6.jpg) 因此,在制作spu之前要先确定当前商品有哪些销售属性! ## 2.2 spu数据结构图 ![img](assets/day03/wps7.jpg) # 3. 商品SPU列表功能 > YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/187 ## 3.1 生成基础代码 在代码生成器中,生成商品SPU相关的表基础代码到`service-product`商品微服务模块,相关表名称如下 - spu_info: 商品SPU信息表 - spu_sale_attr: spu销售属性表 - spu_sale_attr_value:spu销售属性值表 - spu_image:spu商品图片表 - spu_poster:spu商品海报表 - base_sale_attr:基本销售属性表 但是这里我们不建议生成以上相关表三层类,这样同时生成的三层类过于多,而以上的6张表都跟商品SPU相关,故建议只提供对应的实体类跟持久层接口。业务层/持久层我们提供一个抽取SpuManagerXxx 注意:在`mybatis-plus-code`代码生成器模块中`CodeGenerator`设置禁用模板来达到目标 ![image-20221212231530167](assets/image-20221212231530167.png) ## 3.2 控制器SpuManageController ```java package com.atguigu.gmall.product.controller; import com.atguigu.gmall.common.result.Result; import com.atguigu.gmall.product.model.SpuInfo; import com.atguigu.gmall.product.service.SpuManageService; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author: atguigu * @create: 2022-12-24 14:11 */ @RestController @RequestMapping("/admin/product") public class SpuManageController { @Autowired private SpuManageService spuManageService; /** * spu分页列表 * * @param page * @param size * @param spuInfo * @return */ @GetMapping("/{page}/{size}") public Result> getSpuInfoPage( @PathVariable("page") Long page, @PathVariable("size") Long size, SpuInfo spuInfo) { //1.构建分页对象 IPage iPage = new Page<>(page, size); //2.调用业务逻辑 iPage = spuManageService.getSpuInfoPage(iPage, spuInfo); return Result.ok(iPage); } } ``` ## 3.3 业务接口SpuManageService ```java package com.atguigu.gmall.product.service; import com.atguigu.gmall.product.model.SpuInfo; import com.baomidou.mybatisplus.core.metadata.IPage; public interface SpuManageService { /** * 根据分类ID查询Spu分页列表 * @param iPage * @param spuInfo * @return */ IPage getSpuInfoPage(IPage iPage, SpuInfo spuInfo); } ``` ## 3.4 业务实现类 SpuManageServiceImpl ```java package com.atguigu.gmall.product.service.impl; import com.atguigu.gmall.product.mapper.SpuInfoMapper; import com.atguigu.gmall.product.model.SpuInfo; import com.atguigu.gmall.product.service.SpuManageService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author: atguigu * @create: 2022-12-24 14:10 */ @Service public class SpuManageServiceImpl implements SpuManageService { @Autowired private SpuInfoMapper spuInfoMapper; /** * 根据分类ID查询Spu分页列表 * * @param iPage * @param spuInfo * @return */ @Override public IPage getSpuInfoPage(IPage iPage, SpuInfo spuInfo) { //1.构建page对象 已传入 //2.构建分页查询条件 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); if (spuInfo.getCategory3Id() != null) { queryWrapper.eq(SpuInfo::getCategory3Id, spuInfo.getCategory3Id()); } //根据修改时间进行倒序 queryWrapper.orderByDesc(SpuInfo::getUpdateTime); //3.执行分页返回 return spuInfoMapper.selectPage(iPage, queryWrapper); } } ``` ## 3.5 创建mapper ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.SpuInfo; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface SpuInfoMapper extends BaseMapper { } ``` # 4. Spu的保存功能中的图片上传 ## 4.1 MinIO介绍 MinIO 是一个基于Apache License v3.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。 MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。 官方文档:http://docs.minio.org.cn/docs 旧一点 中文 https://docs.min.io/ 新 英文 ## 4.2 应用场景 ### 4.2.1 单主机单硬盘模式 ![img](assets/day03/wps8.jpg) ### 4.2.2 单主机多硬盘模式 ![img](assets/day03/wps9.jpg) ### 4.2.3 多主机多硬盘分布式 ![img](assets/day03/wps10.jpg) ## 4.3 特点 · 高性能:作为高性能对象存储,在标准硬件条件下它能达到55GB/s的读、35GB/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:9001/minio/login,如图: ![img](assets/day03/wps11.jpg) 登录账户说明:安装时指定了**登录账号**:admin/admin123456 **注意**:文件上传时,需要调整一下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 ## 4.6 利用Java客户端调用Minio 参考文档:https://docs.min.io/docs/java-client-api-reference.html ### 4.6.1 引入依赖 在`service-product`模块中添加依赖 ```xml io.minio minio 8.2.0 ``` ### 4.6.2 添加配置信息 在nacos 配置中心列表中的`service-product-dev.yaml`增加以下信息! ```yaml minio: endpointUrl: http://192.168.200.128:9000 accessKey: admin secreKey: admin123456 bucketName: gmall ``` ### 4.6.3 创建FileUploadController控制器 > YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/427 ```java package com.atguigu.gmall.product.controller; import com.atguigu.gmall.common.result.Result; import com.atguigu.gmall.product.service.FileUploadService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("admin/product") public class FileUploadController { @Autowired private FileUploadService fileUploadService; /** * 文件上传 * @param file 上传的文件 * @return * @throws Exception */ @PostMapping("fileUpload") public Result fileUpload(MultipartFile file) throws Exception { String url = fileUploadService.upload(file); // 将文件上传之后的路径返回给页面! return Result.ok(url); } } ``` ### 4.6.4 创建FileUploadService ```java package com.atguigu.gmall.product.service; import org.springframework.web.multipart.MultipartFile; public interface FileUploadService { /** * 文件上传到MinIO * @param file * @return */ String upload(MultipartFile file); } ``` ### 4.6.5 创建FileUploadServiceImpl ```java package com.atguigu.gmall.product.service.impl; import com.atguigu.gmall.product.service.FileUploadService; import io.minio.*; import io.minio.errors.MinioException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.util.UUID; /** * @author: atguigu * @create: 2022-12-24 15:15 */ @Service public class FileUploadServiceImpl implements FileUploadService { @Value("${minio.endpointUrl}") private String endpointUrl; @Value("${minio.accessKey}") private String accessKey; @Value("${minio.secreKey}") private String secreKey; @Value("${minio.bucketName}") private String bucketName; /** * 将文件上传到MinIO存储空间“gmall”返回图片在线地址 * * @param file 图片文件 * @return */ @Override public String fileUpload(MultipartFile file) { try { //1.创建操作MinIO存储客户端对象 MinioClient minioClient = MinioClient.builder() .endpoint(endpointUrl) .credentials(accessKey, secreKey) .build(); //2.判断存储空间是否存在 boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); if (!found) { //如果存储空间不存在则创建 minioClient.makeBucket(MakeBucketArgs.builder().bucket("asiatrip").build()); } //3.上传文件到MinIO String fileName = System.currentTimeMillis() + UUID.randomUUID().toString(); minioClient.putObject( PutObjectArgs.builder().bucket(bucketName).object(fileName).stream( file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build()); //4.返回在线地址 String url = endpointUrl + "/" + bucketName + "/" + fileName; return url; } catch (Exception e) { System.out.println("Error occurred: " + e); } return null; } } ``` # 5. spu保存 ## 5.1 加载销售属性 需求:在添加SPU商品需要选择当前商品的销售属性 ![image-20221212234221124](assets/image-20221212234221124.png) > YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/307 ### 5.1.1 控制器SpuManageController ```java /** * 加载销售属性列表 * api接口地址 http://api.gmall.com/admin/product/baseSaleAttrList * @return */ @GetMapping("baseSaleAttrList") public Result baseSaleAttrList() { // 查询所有的销售属性集合 List baseSaleAttrList = manageService.getBaseSaleAttrList(); return Result.ok(baseSaleAttrList); } ``` ### 5.1.2 在SpuMangeService添加接口 ```java /** * 查询所有的销售属性数据 * @return */ List getBaseSaleAttrList(); ``` ### 5.1.3. SpuManageServiceImpl实现类 ```java @Autowired private BaseSaleAttrMapper baseSaleAttrMapper; @Override public List getBaseSaleAttrList() { return baseSaleAttrMapper.selectList(null); } ``` ### 5.1.4. 创建mapper ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.BaseSaleAttr; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface BaseSaleAttrMapper extends BaseMapper { } ``` ## 5.2 加载品牌数据 > YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/171 BaseCategoryTrademarkController 完成 ## 5.3 保存后台代码 > YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/219 ### 5.3.1 控制器SpuManageController ```java /** * 保存spu * * @param spuInfo * @return */ @PostMapping("saveSpuInfo") public Result saveSpuInfo(@RequestBody SpuInfo spuInfo) { // 调用服务层的保存方法 spuManageService.saveSpuInfo(spuInfo); return Result.ok(); } ``` ### 5.3.2 业务接口SpuManageService ```java /** * 保存商品SPU数据 * @param spuInfo */ void saveSpuInfo(SpuInfo spuInfo); ``` ### 5.3.3. 业务实现类SpuManageServiceImpl ```java @Autowired private SpuImageMapper spuImageMapper; @Autowired private SpuSaleAttrMapper spuSaleAttrMapper; @Autowired private SpuSaleAttrValueMapper spuSaleAttrValueMapper; @Autowired private SpuPosterMapper spuPosterMapper; /** * 保存商品SPU信息 * 1.保存商品基本信息到spu_info表 * 2.保存商品图片到spu_image表 关联到商品spu * 3.保存商品海报图片到spu_poster表 关联到商品spu * 4.保存商品Spu对应的销售属性名称到spu_sale_attr表中 。。 * 5.保存商品Spu对应销售属性值表到spu_sale_attr_value表 。。 * * @param spuInfo */ @Override @Transactional(rollbackFor = Exception.class) public void saveSpuInfo(SpuInfo spuInfo) { //1.保存商品基本信息到spu_info表 商品信息对象中主键属性有值 spuInfoMapper.insert(spuInfo); //2.保存商品图片到spu_image表 关联到商品spu if (!CollectionUtils.isEmpty(spuInfo.getSpuImageList())) { for (SpuImage spuImage : spuInfo.getSpuImageList()) { //将图片关联到商品SPU spuImage.setSpuId(spuInfo.getId()); spuImageMapper.insert(spuImage); } } //3.保存商品海报图片到spu_poster表 关联到商品spu if (!CollectionUtils.isEmpty(spuInfo.getSpuPosterList())) { for (SpuPoster spuPoster : spuInfo.getSpuPosterList()) { spuPoster.setSpuId(spuInfo.getId()); spuPosterMapper.insert(spuPoster); } } //4.保存商品Spu对应的销售属性名称到spu_sale_attr表中 。。 if (!CollectionUtils.isEmpty(spuInfo.getSpuSaleAttrList())) { for (SpuSaleAttr spuSaleAttr : spuInfo.getSpuSaleAttrList()) { //4.1 处理销售属性名称对象 关联商品SPU spuSaleAttr.setSpuId(spuInfo.getId()); //4.2 执行保存spu销售属性名称 spuSaleAttrMapper.insert(spuSaleAttr); //5.保存商品Spu对应销售属性值表到spu_sale_attr_value表 。。 //处理当前spu销售属性对应商品销售属性值集合 if (!CollectionUtils.isEmpty(spuSaleAttr.getSpuSaleAttrValueList())) { for (SpuSaleAttrValue spuSaleAttrValue : spuSaleAttr.getSpuSaleAttrValueList()) { //手动关联商品SpuID spuSaleAttrValue.setSpuId(spuInfo.getId()); //设置当前销售属性值对应属性名称 spuSaleAttrValue.setSaleAttrName(spuSaleAttr.getSaleAttrName()); spuSaleAttrValueMapper.insert(spuSaleAttrValue); } } } } } ``` ### 5.3.4 Mapper 建立对应的mapper 文件 ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.SpuImage; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface SpuImageMapper extends BaseMapper { } ``` ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.SpuSaleAttr; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface SpuSaleAttrMapper extends BaseMapper { } ``` ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.SpuSaleAttrValue; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface SpuSaleAttrValueMapper extends BaseMapper { } ``` ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.SpuPoster; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface SpuPosterMapper extends BaseMapper { } ```