一、spu相关业务介绍
1.1 销售属性
销售属性,就是商品详情页右边,可以通过销售属性来定位一组spu下的哪款sku。可以让当前的商品详情页,跳转到自己的“兄弟”商品。
一般每种商品的销售属性不会太多,大约1-4种。整个电商的销售属性种类也不会太多,大概10种以内。比如:颜色、尺寸、版本、套装等等。不同销售属性的组合也就构成了一个spu下多个sku的结构。
因此,在制作spu之前要先确定当前商品有哪些销售属性!
1.2 spu数据结构图
二、列表查询功能开发
2.1 创建mapper
@Mapper public interface SpuInfoMapper extends BaseMapper } |
---|
2.2 创建接口ManageService
/ spu分页查询 @param **pageParam __ *@param spuInfo __ _ @return ** **/ _IPage |
---|
2.3 创建实现类 ManageServiceImpl
@Autowired private SpuInfoMapper spuInfoMapper; @Overridepublic IPage 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 IPage 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 } |
---|
3.1.3 创建实现类BaseTrademarkServiceImpl
| @Servicepublic class BaseTrademarkServiceImpl extends ServiceImpl
@Autowired
private BaseTrademarkMapper baseTrademarkMapper;
@Override
public IPage
QueryWrapper
queryWrapper.orderByAsc(“id”);
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 IPage 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 保存分类与品牌关联 @param _categoryTrademarkVo __ */ void save(CategoryTrademarkVo categoryTrademarkVo); _/ 获取当前未被三级分类关联的所有品牌 @param **category3Id _ _* @return * / List 删除关联 @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 // 调用mapper 层! @Autowired private BaseTrademarkMapper baseTrademarkMapper; @Autowired private BaseCategoryTrademarkMapper baseCategoryTrademarkMapper; @Override public List // 根据分类Id 获取到品牌Id 集合数据 // select from base_category_trademark where category3_id = ?; _QueryWrapper baseCategoryTrademarkQueryWrapper.eq(“category3_id”,category3Id); List // 判断baseCategoryTrademarkList 这个集合 if(!CollectionUtils._isEmpty(baseCategoryTrademarkList)){ // 需要获取到这个集合中的品牌Id 集合数据 _List 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.eq(“category3_id”,category3Id); baseCategoryTrademarkQueryWrapper.eq(“trademark_id”,trademarkId); baseCategoryTrademarkMapper.delete(baseCategoryTrademarkQueryWrapper); } @Override public List QueryWrapper baseCategoryTrademarkQueryWrapper.eq(“category3_id”,category3Id); List if (!CollectionUtils._isEmpty(baseCategoryTrademarkList)){ // 找到关联的品牌Id 集合数据 {1,3} _List return baseCategoryTrademark.getTrademarkId(); }).collect(Collectors._toList // 在所有的品牌Id 中将这些有关联的品牌Id 给过滤掉就可以! // select from base_trademark; 外面 baseTrademarkMapper.selectList(null) {1,2,3,5} _List *return }).collect(Collectors._toList()); // 返回数据 return baseTrademarkList; } // 如果说这个三级分类Id 下 没有任何品牌! 则获取到所有的品牌数据! return baseTrademarkMapper.selectList(null); } @Override public void save(CategoryTrademarkVo categoryTrademarkVo) { / private Long category3Id; private List category3Id 61 tmId 2; category3Id 61 tmId 5; // 获取到品牌Id 集合数据 _List if (!CollectionUtils._isEmpty(trademarkIdList)){ // 做映射关系 _List 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 // 返回 return Result._ok(list); } @GetMapping(“findCurrentTrademarkList/{category3Id}”) public Result findCurrentTrademarkList(@PathVariable Long category3Id){ List // 返回 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单主机单硬盘模式
4.2.2单主机多硬盘模式
4.2.3多主机多硬盘分布式
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,如图:
说明:安装是指定了登录账号
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 |
---|
实现类 @Autowired private BaseSaleAttrMapper baseSaleAttrMapper; @Override public List 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 return Result._ok } } |
---|
5.2 加载品牌数据
BaseCategoryTrademarkController/ 查询全部品牌 @return * / @GetMapping(“findTrademarkList/{category3Id}”) public Result findTrademarkList(@PathVariable Long category3Id){ // 调用服务层方法 _List } |
---|
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 if (!CollectionUtils._isEmpty(spuImageList)){ // 循环遍历 for (SpuImage spuImage : spuImageList) { // 需要将spuId 赋值 _spuImage.setSpuId(spuInfo.getId()); // 保存spuImge spuImageMapper.insert(spuImage); } } // 获取销售属性集合 List if (!CollectionUtils._isEmpty(spuSaleAttrList)){ // 循环遍历 for (SpuSaleAttr spuSaleAttr : spuSaleAttrList) { // 需要将spuId 赋值 _spuSaleAttr.setSpuId(spuInfo.getId()); spuSaleAttrMapper.insert(spuSaleAttr); // 再此获取销售属性值集合 List 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 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(); } |
---|