第3章-商品SPU管理.md 38 KB

第3章-商品SPU管理

学习目标:

  • 完成品牌管理
  • 了解SPU商品相关的概念
  • 完成SPU列表功能
  • 掌握MinIO分布式文件存储服务
  • 完成商品SPU保存功能(重点难点)

1. 品牌管理

image-20221212234420609

1.1 生成基础代码

在代码生成器中,生成品牌相关的表到service-product商品微服务模块,相关表名称如下

  • base_trademark 品牌表
  • base_category_trademark 分类品牌中间表

image-20221212171431019

1.2 品牌分页查询

需求:

image-20221212204805066

YAPI接口地址:

列表:http://192.168.200.128:3000/project/11/interface/api/371

新增:http://192.168.200.128:3000/project/11/interface/api/347

根据ID查询品牌:http://192.168.200.128:3000/project/11/interface/api/387

删除:http://192.168.200.128:3000/project/11/interface/api/379

1.2.1 控制器BaseTrademarkController

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 page
     * @return
     */
    @ApiOperation("分页查询所有品牌")
    @GetMapping("/baseTrademark/{page}/{limit}")
    public Result getTrademarkByPage(@PathVariable("page") Integer page, @PathVariable("limit") Integer limit) {

        //1.构建分页对象(封装页码跟页大小)
        Page<BaseTrademark> pageInfo = new Page<>(page, limit);
        //2.调用业务层提供分页方法 page方法(分页对象, 查询条件)  - 执行完业务层分页方法后,封装分页对象中(总数,页数,当前页记录)
        pageInfo = baseTrademarkService.getTrademarkByPage(pageInfo);
        return Result.ok(pageInfo);
    }


}

1.2.2 业务接口BaseTrademarkService

package com.atguigu.gmall.product.service;

import com.atguigu.gmall.product.model.BaseTrademark;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * 品牌表 业务接口类
 * @author atguigu
 * @since 2023-06-07
 */
public interface BaseTrademarkService extends IService<BaseTrademark> {


    /**
     * 分页查询所有品牌
     *
     * @param pageInfo
     * @return
     */
    Page<BaseTrademark> getTrademarkByPage(Page<BaseTrademark> iPage);
}

1.2.3 业务实现类BaseTrademarkServiceImpl

package com.atguigu.gmall.product.service.impl;

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.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

/**
 * 品牌表 业务实现类
 *
 * @author atguigu
 * @since 2023-08-30
 */
@Service
public class BaseTrademarkServiceImpl extends ServiceImpl<BaseTrademarkMapper, BaseTrademark> implements BaseTrademarkService {

    /**
     * 分页查询所有品牌
     *
     * @param pageInfo
     * @return
     */
    @Override
    public Page<BaseTrademark> getTrademarkByPage(Page<BaseTrademark> pageInfo) {
        //1.构建条件对象
        LambdaQueryWrapper<BaseTrademark> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(BaseTrademark::getId, BaseTrademark::getLogoUrl, BaseTrademark::getTmName);

        //2.执行分页查询 (必须配置过MP分页拦截器)
        return this.page(pageInfo, queryWrapper);
    }
}

1.2.4 持久层mapper

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<BaseTrademark> {

}

1.2.5 其他业务代码

BaseTrademarkController 中完成品牌的增删改查,调用公共业务接口方法即可。

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.service.BaseTrademarkService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @author: atguigu
 * @create: 2023-06-07 10:13
 */
@RestController
@RequestMapping("/admin/product")
public class BaseTrademarkController {

    @Autowired
    private BaseTrademarkService baseTrademarkService;

       /**
     * 新增品牌
     * @param baseTrademark
     * @return
     */
    @ApiOperation("新增品牌")
    @PostMapping("/baseTrademark/save")
    public Result saveTrademark(@RequestBody BaseTrademark baseTrademark){
        baseTrademarkService.save(baseTrademark);
        return Result.ok();
    }

    /**
     * 新增品牌
     * @param baseTrademark
     * @return
     */
    @ApiOperation("修改品牌")
    @PutMapping("/baseTrademark/update")
    public Result updateTrademark(@RequestBody BaseTrademark baseTrademark){
        baseTrademarkService.updateById(baseTrademark);
        return Result.ok();
    }

    /**
     * 根据品牌Id 获取品牌对象
     * @param id
     * @return
     */
    @ApiOperation("根据品牌Id 获取品牌对象")
    @GetMapping("/baseTrademark/get/{id}")
    public Result getTradeById(@PathVariable("id") Long id){
        BaseTrademark trademark = baseTrademarkService.getById(id);
        return Result.ok(trademark);
    }

    /**
     * 删除品牌
     * @param id
     * @return
     */
    @ApiOperation("删除品牌")
    @DeleteMapping("/baseTrademark/remove/{id}")
    public Result deleteById(@PathVariable("id") Long id){
        baseTrademarkService.removeById(id);
        return Result.ok();
    }
}

2.2 分类品牌管理

YAPI接口地址:

1.2.1 控制器

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 io.swagger.annotations.ApiOperation;
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 2023-08-30
 */
@Api(tags = "分类品牌中间表控制器")
@RestController
@RequestMapping("/admin/product") //TODO 填写基础映射URL
public class BaseCategoryTrademarkController {

    @Autowired
    private BaseCategoryTrademarkService baseCategoryTrademarkService;


    /**
     * 根据category3Id获取品牌列表
     *
     * @param category3Id
     * @return
     */
    @ApiOperation("根据category3Id获取品牌列表")
    @GetMapping("/baseCategoryTrademark/findTrademarkList/{category3Id}")
    public Result getTrademarkList(@PathVariable("category3Id") Long category3Id) {
        List<BaseTrademark> list = baseCategoryTrademarkService.getTrademarkList(category3Id);
        return Result.ok(list);
    }


    /**
     * 根据category3Id获取可选品牌列表(未关联到分类品牌列表)
     *
     * @param category3Id
     * @return
     */
    @ApiOperation("根据category3Id获取可选品牌列表")
    @GetMapping("/baseCategoryTrademark/findCurrentTrademarkList/{category3Id}")
    public Result getCurrentTrademarkList(@PathVariable("category3Id") Long category3Id) {
        List<BaseTrademark> list = baseCategoryTrademarkService.getCurrentTrademarkList(category3Id);
        return Result.ok(list);
    }


    /**
     * 将多个品牌关联到分类
     *
     * @param vo
     * @return
     */
    @ApiOperation("将多个品牌关联到分类")
    @PostMapping("/baseCategoryTrademark/save")
    public Result saveCategoryTradeMark(@RequestBody CategoryTrademarkVo vo) {
        baseCategoryTrademarkService.saveCategoryTradeMark(vo);
        return Result.ok();
    }


    /**
     * 解除品牌关联分类
     * @param category3Id
     * @param trademarkId
     * @return
     */
    @ApiOperation("解除品牌关联分类")
    @DeleteMapping("/baseCategoryTrademark/remove/{category3Id}/{trademarkId}")
    public Result deleteTIdFromCategory(@PathVariable("category3Id") Long category3Id, @PathVariable("trademarkId") Long trademarkId) {
        baseCategoryTrademarkService.deleteTIdFromCategory(category3Id, trademarkId);
        return Result.ok();
    }
}

3.2.2 业务接口

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 2023-08-30
 */
public interface BaseCategoryTrademarkService extends IService<BaseCategoryTrademark> {

    /**
     * 根据category3Id获取品牌列表
     * @param category3Id
     * @return
     */
    List<BaseTrademark> getTrademarkList(Long category3Id);

    /**
     * 根据category3Id获取可选品牌列表(未关联到分类品牌列表)
     * @param category3Id
     * @return
     */
    List<BaseTrademark> getCurrentTrademarkList(Long category3Id);

    /**
     * 将多个品牌关联到分类
     * @param vo
     * @return
     */
    void saveCategoryTradeMark(CategoryTrademarkVo vo);


    /**
     * 解除品牌关联分类
     * @param category3Id
     * @param trademarkId
     * @return
     */
    void deleteTIdFromCategory(Long category3Id, Long trademarkId);
}

3.2.3 业务实现类

package com.atguigu.gmall.product.service.impl;

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.atguigu.gmall.product.service.BaseTrademarkService;
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 2023-08-30
 */
@Service
public class BaseCategoryTrademarkServiceImpl extends ServiceImpl<BaseCategoryTrademarkMapper, BaseCategoryTrademark> implements BaseCategoryTrademarkService {

    @Autowired
    private BaseTrademarkService baseTrademarkService;

    /**
     * 根据category3Id获取品牌列表
     *
     * @param category3Id
     * @return
     */
    @Override
    public List<BaseTrademark> getTrademarkList(Long category3Id) {
        //1.根据分类ID查询中间表得到分类品牌集合
        LambdaQueryWrapper<BaseCategoryTrademark> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(BaseCategoryTrademark::getCategory3Id, category3Id);
        List<BaseCategoryTrademark> categoryTrademarkList = this.list(queryWrapper);

        //2.获取分类品牌集合中品牌ID集合
        if (!CollectionUtils.isEmpty(categoryTrademarkList)) {
            //采用Stream流获取集合中元素品牌ID   将集合泛型从BaseCategoryTrademark转为Long类型
            List<Long> tradeMarkIdList = categoryTrademarkList.stream()
                    .map(BaseCategoryTrademark::getTrademarkId).collect(Collectors.toList());
            //3.根据品牌ID集合查询品牌列表
            List<BaseTrademark> trademarkList = baseTrademarkService.listByIds(tradeMarkIdList);
            return trademarkList;
        }
        return null;
    }

    /**
     * 根据category3Id获取可选品牌列表(未关联到分类品牌列表)
     *
     * @param category3Id
     * @return
     */
    @Override
    public List<BaseTrademark> getCurrentTrademarkList(Long category3Id) {
        return baseMapper.getCurrentTrademarkList(category3Id);
    }

    /**
     * 将多个品牌关联到分类
     *
     * @param vo
     * @return
     */
    @Override
    public void saveCategoryTradeMark(CategoryTrademarkVo vo) {
        //1.获取品牌ID集合
        List<Long> trademarkIdList = vo.getTrademarkIdList();

        //2.将品牌ID集合转为分类品牌对象集合
        if (!CollectionUtils.isEmpty(trademarkIdList)) {
            List<BaseCategoryTrademark> list = trademarkIdList.stream().map(trademarkId -> {
                BaseCategoryTrademark baseCategoryTrademark = new BaseCategoryTrademark();
                baseCategoryTrademark.setTrademarkId(trademarkId);
                baseCategoryTrademark.setCategory3Id(vo.getCategory3Id());
                return baseCategoryTrademark;
            }).collect(Collectors.toList());
            //3.批量保存
            this.saveBatch(list);
        }
    }


    /**
     * 解除品牌关联分类
     *
     * @param category3Id
     * @param trademarkId
     * @return
     */
    @Override
    public void deleteTIdFromCategory(Long category3Id, Long trademarkId) {
        LambdaQueryWrapper<BaseCategoryTrademark> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(BaseCategoryTrademark::getCategory3Id, category3Id)
                .eq(BaseCategoryTrademark::getTrademarkId, trademarkId);
        this.remove(queryWrapper);
    }
}

3.2.4 Mapper

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 java.util.List;

/**
 * 品牌表 Mapper 接口
 *
 * @author atguigu
 * @since 2023-07-25
 */
public interface BaseTrademarkMapper extends BaseMapper<BaseTrademark> {

    /**
     * 查询该分类下未关联的品牌集合
     * @param category3Id
     * @return
     */
    List<BaseTrademark> getCurrentTrademarkList(@Param("category3Id") Long category3Id);

}

动态SQL

BaseCategoryTrademarkMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.gmall.product.mapper.BaseCategoryTrademarkMapper">

    <!--查询指定分类下未关联品牌列表-->
    <select id="getCurrentTrademarkList" resultType="com.atguigu.gmall.product.model.BaseTrademark">
        SELECT
            b.id,
            b.tm_name,
            b.logo_url
        FROM
            base_trademark b
                LEFT JOIN (
                SELECT
                    bt.id
                FROM
                    base_trademark bt
                        INNER JOIN base_category_trademark bct ON bct.trademark_id = bt.id
                WHERE
                    bct.category3_id = #{category3Id}
                  AND bt.is_deleted = 0
                  AND bct.is_deleted = 0
            ) t ON t.id = b.id
        WHERE
            t.id IS NULL
    </select>
</mapper>

2. spu相关业务介绍

2.1 销售属性

销售属性,就是商品详情页右边,可以通过销售属性来定位一组spu下的哪款sku。

img

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

2.2 spu数据结构图

img

3. 商品SPU列表功能

YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/187

3.1 生成基础代码

在代码生成器中,生成商品SPU相关的表基础代码到service-product商品微服务模块,相关表名称如下

  • spu_info: 商品SPU信息表
  • spu_image:spu商品图片表
  • spu_poster:spu商品海报表
  • spu_sale_attr: spu销售属性表
  • spu_sale_attr_value:spu销售属性值表
  • base_sale_attr:基本销售属性表

但是这里我们不建议生成以上相关表三层类,这样同时生成的三层类过于多,而以上的6张表都跟商品SPU相关,故建议只提供对应的实体类跟持久层接口。业务层/持久层我们提供一个抽取SpuManagerXxx

注意:在mybatis-plus-code代码生成器模块中CodeGenerator设置禁用模板来达到目标

image-20221212231530167

3.2 控制器SpuManageController

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.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @author: atguigu
 * @create: 2023-06-07 14:42
 */
@RestController
@RequestMapping("/admin/product")
public class SpuManageController {

    @Autowired
    private SpuManageService spuManageService;


    /**
     * 商品SPU分页
     *
     * @param page
     * @param size
     * @return
     */
    @GetMapping("/{page}/{size}")
    public Result getSpuByPage(@PathVariable("page") long page, @PathVariable("size") long size, @RequestParam("category3Id") Long category3Id) {
        //1.构建分页对象
        Page<SpuInfo> spuInfoPage = new Page<>(page, size);
        //2.调用业务层实现分页
        spuInfoPage = spuManageService.getSpuByPage(spuInfoPage, category3Id);
        return Result.ok(spuInfoPage);
    }
}

3.3 业务接口SpuManageService

package com.atguigu.gmall.product.service;

import com.atguigu.gmall.product.model.SpuInfo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

public interface SpuManageService {

    /**
     * 商品SPU分页
     *
     * @param spuInfoPage MP分页对象
     * @param category3Id 分类ID
     * @return
     */
    Page<SpuInfo> getSpuByPage(Page<SpuInfo> spuInfoPage, Long category3Id);
}

3.4 业务实现类 SpuManageServiceImpl

package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.product.model.SpuInfo;
import com.atguigu.gmall.product.service.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author: atguigu
 * @create: 2023-06-07 14:39
 */
@Service
public class SpuManageServiceImpl implements SpuManageService {


    @Autowired
    private SpuInfoService spuInfoService;

    @Autowired
    private SpuImageService spuImageService;

    @Autowired
    private SpuPosterService spuPosterService;

    @Autowired
    private SpuSaleAttrService spuSaleAttrService;

    @Autowired
    private SpuSaleAttrValueService spuSaleAttrValueService;

    @Autowired
    private BaseSaleAttrService baseSaleAttrService;


    /**
     * 商品SPU分页
     *
     * @param spuInfoPage MP分页对象
     * @param category3Id 分类ID
     * @return
     */
    @Override
    public Page<SpuInfo> getSpuByPage(Page<SpuInfo> spuInfoPage, Long category3Id) {
        //1.构建分页对象 入参已提供
        //2.构建查询条件
        LambdaQueryWrapper<SpuInfo> queryWrapper = new LambdaQueryWrapper<>();
        if (category3Id != null) {
            queryWrapper.eq(SpuInfo::getCategory3Id, category3Id);
        }
        queryWrapper.orderByDesc(SpuInfo::getUpdateTime);
        //3.执行分页查询
        return spuInfoService.page(spuInfoPage, queryWrapper);
    }
}

3.5 创建mapper

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<SpuInfo> {

}

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

4.2.2 单主机多硬盘模式

img

4.2.3 多主机多硬盘分布式

img

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

登录账户说明:安装时指定了登录账号:admin/admin123456

注意:文件上传时,需要调整一下linux 服务器的时间与windows 时间一致!

第一步:安装ntp服务 yum -y install ntp

第二步:开启开机启动服务 systemctl enable ntpd

第三步:启动服务,如果有问题先停止服务 systemctl stop ntpd ,再启动 执行后续的四、五、六的命令 systemctl start ntpd

第四步:更改时区 timedatectl set-timezone Asia/Shanghai

第五步:启用ntp同步 timedatectl set-ntp yes

第六步:同步时间 ntpq -p

定时自动同步

echo "*/2 * * * * /usr/sbin/ntpdate us.pool.ntp.org | logger -t NTP" >> /tmp/crontab.bak


crontab /tmp/crontab.bak

4.6 利用Java客户端调用Minio

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

4.6.1 引入依赖

service-product模块中添加依赖

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

4.6.2 添加配置信息

在nacos 配置中心列表中的service-product-dev.yaml增加以下信息!

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

service-product模块中新建config包 提供配置类注册客户端对象

package com.atguigu.gmall.product.config;

import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author: atguigu
 * @create: 2023-04-18 15:19
 */
@Configuration
public class MinIOConfig {


    @Value("${minio.endpointUrl}")
    private String endpointUrl;

    @Value("${minio.accessKey}")
    private String accessKey;


    @Value("${minio.secreKey}")
    private String secreKey;


    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .endpoint(endpointUrl)
                .credentials(accessKey, secreKey)
                .build();
    }

}

4.6.3 创建FileUploadController控制器

YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/427

TIPS:修改Tomcat默认上传文件大小限制

# 设置最大文件上传大小为5MB
spring:
  servlet:
    multipart:
      max-file-size: 5MB
      max-request-size: 5MB
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;

/**
 * @author: atguigu
 * @create: 2023-08-30 11:40
 */
@RestController
@RequestMapping("/admin/product")
public class FileUploadController {

    @Autowired
    private FileUploadService fileUploadService;


    /**
     * 文件上传接口
     *
     * @param file
     * @return
     */
    @PostMapping("/fileUpload")
    public Result fileUpload(MultipartFile file) {
        String fileUrl = fileUploadService.uploadImage(file);
        return Result.ok(fileUrl);
    }

}

4.6.4 创建FileUploadService

package com.atguigu.gmall.product.service;

import org.springframework.web.multipart.MultipartFile;

public interface FileUploadService {

    /**
     * 上传图片到MInIO
     * @param file
     * @return 在线地址
     */
    String uploadImage(MultipartFile file);
}

4.6.5 创建FileUploadServiceImpl

package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.product.service.FileUploadService;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Date;
import java.util.UUID;

/**
 * @author: atguigu
 * @create: 2023-08-30 11:41
 */
@Slf4j
@Service
public class FileUploadServiceImpl implements FileUploadService {

    @Autowired
    private MinioClient minioClient;

    @Value("${minio.bucketName}")
    private String bucketName;

    @Value("${minio.endpointUrl}")
    private String endpointUrl;


    /**
     * 上传图片到MInIO
     *
     * @param file
     * @return 在线地址
     */
    @Override
    public String uploadImage(MultipartFile file) {
        try {
            //1.验证文件是否为图片(校验后缀MIME,文件内容 校验文件头信息)
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            if (bufferedImage == null) {
                throw new RuntimeException("图片格式非法!");
            }

            //2.构建上传后文件名称 形式: /yyyy-MM-dd/文件名称.png
            String folderName = DateUtil.formatDate(new Date());
            String fileName = UUID.randomUUID().toString().replaceAll("-", "");
            String extName = FilenameUtils.getExtension(file.getOriginalFilename());
            String objectName = "/" + folderName + "/" + fileName + "." + extName;

            //3.调用MinIoClient对象完车文件上传
            minioClient.putObject(
                    PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(
                                    file.getInputStream(), file.getSize(), -1)
                            .contentType(file.getContentType())
                            .build());
            //4.拼接上传后文件访问地址
            return endpointUrl + "/" + bucketName + objectName;
        } catch (Exception e) {
            log.error("[商品服务]文件上传失败:{}", e);
            throw new RuntimeException(e);
        }
    }
}

5. spu保存

5.1 加载销售属性

需求:在添加SPU商品需要选择当前商品的销售属性

image-20221212234221124

YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/307

5.1.1 控制器BaseSaleAttrController

package com.atguigu.gmall.product.controller;


import com.atguigu.gmall.common.result.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.atguigu.gmall.product.service.BaseSaleAttrService;

import org.springframework.web.bind.annotation.RestController;

/**
 * 基本销售属性表 前端控制器
 *
 * @author atguigu
 * @since 2023-08-29
 */
@Api(tags = "基本销售属性表控制器")
@RestController
@RequestMapping("/admin/product")
public class BaseSaleAttrController {

    @Autowired
    private BaseSaleAttrService baseSaleAttrService;

    /**
     * 加载当前商城所有销售属性名称列表
     * @return
     */
    @ApiOperation("加载当前商城所有销售属性名称列表")
    @GetMapping("/baseSaleAttrList")
    public Result getBaseAttrList(){
        return Result.ok(baseSaleAttrService.list());
    }
}

5.2 加载品牌数据

YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/171

BaseTrademarkController 已完成

5.3 保存后台代码

YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/219

5.3.1 控制器SpuManageController

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.extension.plugins.pagination.Page;
import io.swagger.annotations.ApiOperation;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.ReactiveSubscription;
import org.springframework.web.bind.annotation.*;

/**
 * @author: atguigu
 * @create: 2023-08-30 14:28
 */
@RestController
@RequestMapping("/admin/product")
public class SpuManageController {

    @Autowired
    private SpuManageService spuManageService;


    /**
     * 商品SPU保存
     * @param spuInfo
     * @return
     */
    @ApiOperation("商品SPU保存")
    @PostMapping("/saveSpuInfo")
    public Result saveSpuInfo(@RequestBody SpuInfo spuInfo){
        spuManageService.saveSpuInfo(spuInfo);
        return Result.ok();
    }
}

5.3.2 业务接口SpuManageService

package com.atguigu.gmall.product.service;

import com.atguigu.gmall.product.model.SpuInfo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

public interface SpuManageService {
    /**
     * 商品SPU保存
     * @param spuInfo
     * @return
     */
    void saveSpuInfo(SpuInfo spuInfo);
}

5.3.3. 业务实现类SpuManageServiceImpl

package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.product.model.*;
import com.atguigu.gmall.product.service.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * @author: atguigu
 * @create: 2023-08-30 14:31
 */
@Service
public class SpuManageServiceImpl implements SpuManageService {

    @Autowired
    private SpuInfoService spuInfoService;

    @Autowired
    private SpuImageService spuImageService;

    @Autowired
    private SpuPosterService spuPosterService;

    @Autowired
    private SpuSaleAttrService spuSaleAttrService;

    @Autowired
    private SpuSaleAttrValueService spuSaleAttrValueService;


    /**
     * 商品SPU保存
     * 1.将商品基本信息封装SpuInfo对象 存入spu_info表
     * 2.将商品图片列表封装SpuImage集合 批量存入spu_image表
     * 3.将商品海报列表封装SpuPost集合 批量存入spu_poster表
     * 4.将商品销售属性列表封装SpuSaleAttr集合 批量存入spu_sale_attr表
     * 5.将某个销售包含属性值列表封装SpuSaleAttrValue集合 批量存入spu_sale_attr_value表
     *
     * @param spuInfo
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveSpuInfo(SpuInfo spuInfo) {
        //1.将商品基本信息封装SpuInfo对象 存入spu_info表
        spuInfoService.save(spuInfo);
        Long spuId = spuInfo.getId();

        //2.将商品图片列表封装SpuImage集合 批量存入spu_image表
        List<SpuImage> spuImageList = spuInfo.getSpuImageList();
        if (!CollectionUtils.isEmpty(spuImageList)) {
            spuImageList.forEach(spuImage -> {
                //将商品图片关联到SPU
                spuImage.setSpuId(spuId);
            });
            spuImageService.saveBatch(spuImageList);
        }

        //3.将商品海报列表封装SpuPost集合 批量存入spu_poster表
        List<SpuPoster> spuPosterList = spuInfo.getSpuPosterList();
        if (!CollectionUtils.isEmpty(spuPosterList)) {
            spuPosterList.forEach(spuImage -> {
                //将海报图片关联到SPU
                spuImage.setSpuId(spuId);
            });
            spuPosterService.saveBatch(spuPosterList);
        }

        //4.将商品销售属性列表封装SpuSaleAttr集合 批量存入spu_sale_attr表
        List<SpuSaleAttr> spuSaleAttrList = spuInfo.getSpuSaleAttrList();
        if (!CollectionUtils.isEmpty(spuSaleAttrList)) {
            for (SpuSaleAttr spuSaleAttr : spuSaleAttrList) {
                //销售属性关联商品SPUID
                spuSaleAttr.setSpuId(spuId);
                //5.将某个销售包含属性值列表封装SpuSaleAttrValue集合 批量存入spu_sale_attr_value表
                List<SpuSaleAttrValue> spuSaleAttrValueList = spuSaleAttr.getSpuSaleAttrValueList();
                if (!CollectionUtils.isEmpty(spuSaleAttrValueList)) {
                    for (SpuSaleAttrValue spuSaleAttrValue : spuSaleAttrValueList) {
                        //销售属性值关联商品
                        spuSaleAttrValue.setSpuId(spuId);
                        //销售属性值设置销售属性名称“冗余”
                        spuSaleAttrValue.setSaleAttrName(spuSaleAttr.getSaleAttrName());
                    }
                    spuSaleAttrValueService.saveBatch(spuSaleAttrValueList);
                }
            }
            spuSaleAttrService.saveBatch(spuSaleAttrList);
        }
    }
}

5.3.4 Mapper

建立对应的mapper 文件

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<SpuImage> {
}
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<SpuSaleAttr> {

}

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<SpuSaleAttrValue> {
}
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<SpuPoster> {
}