基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六)
作者:mmseoamin日期:2023-12-05

新增菜品

    • 1.1 需求分析与设计
      • 1.1.1 产品原型
      • 1.1.2 接口设计
      • 1.1.3 表设计
      • 2.2 代码开发
        • 2.2.1 文件上传实现
        • 2.2.2 新增菜品实现
        • 2.3 功能测试

          1.1 需求分析与设计

          1.1.1 产品原型

          后台系统中可以管理菜品信息,通过 新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片。

          新增菜品原型:

          基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六),在这里插入图片描述,第1张

          当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。

          业务规则:

          • 菜品名称必须是唯一的
          • 菜品必须属于某个分类下,不能单独存在
          • 新增菜品时可以根据情况选择菜品的口味
          • 每个菜品必须对应一张图片

            1.1.2 接口设计

            根据上述原型图先粗粒度设计接口,共包含3个接口。

            接口设计:

            • 根据类型查询分类(已完成)
            • 文件上传
            • 新增菜品

              接下来细粒度分析每个接口,明确每个接口的请求方式、请求路径、传入参数和返回值。

              1. 根据类型查询分类

              基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六),在这里插入图片描述,第2张

              基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六),在这里插入图片描述,第3张

              2. 文件上传

              基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六),在这里插入图片描述,第4张

              基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六),在这里插入图片描述,第5张

              3. 新增菜品

              基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六),在这里插入图片描述,第6张

              基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六),在这里插入图片描述,第7张

              基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六),在这里插入图片描述,第8张

              1.1.3 表设计

              通过原型图进行分析:

              基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六),在这里插入图片描述,第9张

              新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。所以在新增菜品时,涉及到两个表:

              表名说明
              dish菜品表
              dish_flavor菜品口味表

              1). 菜品表:dish

              字段名数据类型说明备注
              idbigint主键自增
              namevarchar(32)菜品名称唯一
              category_idbigint分类id逻辑外键
              pricedecimal(10,2)菜品价格
              imagevarchar(255)图片路径
              descriptionvarchar(255)菜品描述
              statusint售卖状态1起售 0停售
              create_timedatetime创建时间
              update_timedatetime最后修改时间
              create_userbigint创建人id
              update_userbigint最后修改人id

              2). 菜品口味表:dish_flavor

              字段名数据类型说明备注
              idbigint主键自增
              dish_idbigint菜品id逻辑外键
              namevarchar(32)口味名称
              valuevarchar(255)口味值

              2.2 代码开发

              2.2.1 文件上传实现

              因为在新增菜品时,需要上传菜品对应的图片(文件),包括后绪其它功能也会使用到文件上传,故要实现通用的文件上传接口。

              文件上传,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛,我们经常发抖音、发朋友圈都用到了文件上传功能。

              实现文件上传服务,需要有存储的支持,那么我们的解决方案将以下几种:

              1. 直接将图片保存到服务的硬盘(springmvc中的文件上传)
                1. 优点:开发便捷,成本低
                2. 缺点:扩容困难
              2. 使用分布式文件系统进行存储
                1. 优点:容易实现扩容
                2. 缺点:开发复杂度稍大(有成熟的产品可以使用,比如:FastDFS,MinIO)
              3. 使用第三方的存储服务(例如OSS)
                1. 优点:开发简单,拥有强大功能,免维护
                2. 缺点:付费

              在本项目选用阿里云的OSS服务进行文件存储。

              基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六),在这里插入图片描述,第10张

              实现步骤:

              1). 定义OSS相关配置

              在sky-server模块

              application-dev.yml

              sky:
                alioss:
                  endpoint: oss-cn-hangzhou.aliyuncs.com
                  access-key-id: **************************
                  access-key-secret: *******************
                  bucket-name: sky-take-out
              

              application.yml

              spring:
                profiles:
                  active: dev    #设置环境
              sky:
                alioss:
                  endpoint: ${sky.alioss.endpoint}
                  access-key-id: ${sky.alioss.access-key-id}
                  access-key-secret: ${sky.alioss.access-key-secret}
                  bucket-name: ${sky.alioss.bucket-name}
              

              2). 读取OSS配置

              在sky-common模块中,已定义

              package com.sky.properties;
              import lombok.Data;
              import org.springframework.boot.context.properties.ConfigurationProperties;
              import org.springframework.stereotype.Component;
              @Component
              @ConfigurationProperties(prefix = "sky.alioss")
              @Data
              public class AliOssProperties {
                  private String endpoint;
                  private String accessKeyId;
                  private String accessKeySecret;
                  private String bucketName;
              }
              

              3). 生成OSS工具类对象

              在sky-server模块

              package com.sky.config;
              /**
               * 配置类,用于创建AliOssUtil对象
               */
              @Configuration
              @Slf4j
              public class OssConfiguration {
                  @Bean
                  @ConditionalOnMissingBean
                  public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
                      log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties);
                      return new AliOssUtil(aliOssProperties.getEndpoint(),
                              aliOssProperties.getAccessKeyId(),
                              aliOssProperties.getAccessKeySecret(),
                              aliOssProperties.getBucketName());
                  }
              }
              

              其中,AliOssUtil.java已在sky-common模块中定义

              package com.sky.utils;
              @Data
              @AllArgsConstructor
              @Slf4j
              public class AliOssUtil {
                  private String endpoint;
                  private String accessKeyId;
                  private String accessKeySecret;
                  private String bucketName;
                  /**
                   * 文件上传
                   *
                   * @param bytes
                   * @param objectName
                   * @return
                   */
                  public String upload(byte[] bytes, String objectName) {
                      // 创建OSSClient实例。
                      OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
                      try {
                          // 创建PutObject请求。
                          ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
                      } catch (OSSException oe) {
                          System.out.println("Caught an OSSException, which means your request made it to OSS, "
                                  + "but was rejected with an error response for some reason.");
                          System.out.println("Error Message:" + oe.getErrorMessage());
                          System.out.println("Error Code:" + oe.getErrorCode());
                          System.out.println("Request ID:" + oe.getRequestId());
                          System.out.println("Host ID:" + oe.getHostId());
                      } catch (ClientException ce) {
                          System.out.println("Caught an ClientException, which means the client encountered "
                                  + "a serious internal problem while trying to communicate with OSS, "
                                  + "such as not being able to access the network.");
                          System.out.println("Error Message:" + ce.getMessage());
                      } finally {
                          if (ossClient != null) {
                              ossClient.shutdown();
                          }
                      }
                      //文件访问路径规则 https://BucketName.Endpoint/ObjectName
                      StringBuilder stringBuilder = new StringBuilder("https://");
                      stringBuilder
                              .append(bucketName)
                              .append(".")
                              .append(endpoint)
                              .append("/")
                              .append(objectName);
                      log.info("文件上传到:{}", stringBuilder.toString());
                      return stringBuilder.toString();
                  }
              }
              

              4). 定义文件上传接口

              在sky-server模块中定义接口

              package com.sky.controller.admin;
              /**
               * 通用接口
               */
              @RestController
              @RequestMapping("/admin/common")
              @Api(tags = "通用接口")
              @Slf4j
              public class CommonController {
                  @Autowired
                  private AliOssUtil aliOssUtil;
                  /**
                   * 文件上传
                   * @param file
                   * @return
                   */
                  @PostMapping("/upload")
                  @ApiOperation("文件上传")
                  public Result upload(MultipartFile file){
                      log.info("文件上传:{}",file);
                      try {
                          //原始文件名
                          String originalFilename = file.getOriginalFilename();
                          //截取原始文件名的后缀   dfdfdf.png
                          String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
                          //构造新文件名称
                          String objectName = UUID.randomUUID().toString() + extension;
                          //文件的请求路径
                          String filePath = aliOssUtil.upload(file.getBytes(), objectName);
                          return Result.success(filePath);
                      } catch (IOException e) {
                          log.error("文件上传失败:{}", e);
                      }
                      return Result.error(MessageConstant.UPLOAD_FAILED);
                  }
              }
              

              2.2.2 新增菜品实现

              1). 设计DTO类

              在sky-pojo模块中

              package com.sky.dto;
              @Data
              public class DishDTO implements Serializable {
                  private Long id;
                  //菜品名称
                  private String name;
                  //菜品分类id
                  private Long categoryId;
                  //菜品价格
                  private BigDecimal price;
                  //图片
                  private String image;
                  //描述信息
                  private String description;
                  //0 停售 1 起售
                  private Integer status;
                  //口味
                  private List flavors = new ArrayList<>();
              }
              

              2). Controller层

              进入到sky-server模块

              package com.sky.controller.admin;
              /**
               * 菜品管理
               */
              @RestController
              @RequestMapping("/admin/dish")
              @Api(tags = "菜品相关接口")
              @Slf4j
              public class DishController {
                  @Autowired
                  private DishService dishService;
                  /**
                   * 新增菜品
                   *
                   * @param dishDTO
                   * @return
                   */
                  @PostMapping
                  @ApiOperation("新增菜品")
                  public Result save(@RequestBody DishDTO dishDTO) {
                      log.info("新增菜品:{}", dishDTO);
                      dishService.saveWithFlavor(dishDTO);//后绪步骤开发
                      return Result.success();
                  }
              }
              

              3). Service层接口

              package com.sky.service;
              import com.sky.dto.DishDTO;
              import com.sky.entity.Dish;
              public interface DishService {
                  /**
                   * 新增菜品和对应的口味
                   *
                   * @param dishDTO
                   */
                  public void saveWithFlavor(DishDTO dishDTO);
              }
              

              4). Service层实现类

              package com.sky.service.impl;
              @Service
              @Slf4j
              public class DishServiceImpl implements DishService {
                  @Autowired
                  private DishMapper dishMapper;
                  @Autowired
                  private DishFlavorMapper dishFlavorMapper;
                  /**
                   * 新增菜品和对应的口味
                   *
                   * @param dishDTO
                   */
                  @Transactional
                  public void saveWithFlavor(DishDTO dishDTO) {
                      Dish dish = new Dish();
                      BeanUtils.copyProperties(dishDTO, dish);
                      //向菜品表插入1条数据
                      dishMapper.insert(dish);//后绪步骤实现
                      //获取insert语句生成的主键值
                      Long dishId = dish.getId();
                      List flavors = dishDTO.getFlavors();
                      if (flavors != null && flavors.size() > 0) {
                          flavors.forEach(dishFlavor -> {
                              dishFlavor.setDishId(dishId);
                          });
                          //向口味表插入n条数据
                          dishFlavorMapper.insertBatch(flavors);//后绪步骤实现
                      }
                  }
              }
              

              5). Mapper层

              DishMapper.java中添加

              	/**
                   * 插入菜品数据
                   *
                   * @param dish
                   */
                  @AutoFill(value = OperationType.INSERT)
                  void insert(Dish dish);
              

              在/resources/mapper中创建DishMapper.xml

              
              
              
                  
                      insert into dish (name, category_id, price, image, description, create_time, update_time, create_user,update_user, status)
                      values (#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}, #{status})
                  
              
              

              DishFlavorMapper.java

              package com.sky.mapper;
              import com.sky.entity.DishFlavor;
              import java.util.List;
              @Mapper
              public interface DishFlavorMapper {
                  /**
                   * 批量插入口味数据
                   * @param flavors
                   */
                  void insertBatch(List flavors);
              }
              

              在/resources/mapper中创建DishFlavorMapper.xml

              
              
              
                  
                      insert into dish_flavor (dish_id, name, value) VALUES
                      
                          (#{df.dishId},#{df.name},#{df.value})
                      
                  
              
              

              2.3 功能测试

              进入到菜品管理—>新建菜品

              基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六),在这里插入图片描述,第11张

              由于没有实现菜品查询功能,所以保存后,暂且在表中查看添加的数据。

              dish表:

              基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六),在这里插入图片描述,第12张

              dish_flavor表:

              基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六),在这里插入图片描述,第13张

              测试成功。

              后记

              👉👉💕💕美好的一天,到此结束,下次继续努力!欲知后续,请看下回分解,写作不易,感谢大家的支持!! 🌹🌹🌹