• 基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(六)


    1.1 需求分析与设计

    1.1.1 产品原型

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

    新增菜品原型:

    在这里插入图片描述

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

    业务规则:

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

    1.1.2 接口设计

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

    接口设计:

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

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

    1. 根据类型查询分类

    在这里插入图片描述
    在这里插入图片描述

    2. 文件上传

    在这里插入图片描述
    在这里插入图片描述

    3. 新增菜品

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    1.1.3 表设计

    通过原型图进行分析:

    在这里插入图片描述

    新增菜品,其实就是将新增页面录入的菜品信息插入到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服务进行文件存储。
    在这里插入图片描述

    实现步骤:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    其中,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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    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<String> 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    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<DishFlavor> flavors = new ArrayList<>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    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);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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<DishFlavor> flavors = dishDTO.getFlavors();
            if (flavors != null && flavors.size() > 0) {
                flavors.forEach(dishFlavor -> {
                    dishFlavor.setDishId(dishId);
                });
                //向口味表插入n条数据
                dishFlavorMapper.insertBatch(flavors);//后绪步骤实现
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    5). Mapper层

    DishMapper.java中添加

    	/**
         * 插入菜品数据
         *
         * @param dish
         */
        @AutoFill(value = OperationType.INSERT)
        void insert(Dish dish);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在/resources/mapper中创建DishMapper.xml

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="com.sky.mapper.DishMapper">
    
        <insert id="insert" useGeneratedKeys="true" keyProperty="id">
            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})
        insert>
    mapper>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    DishFlavorMapper.java

    package com.sky.mapper;
    
    import com.sky.entity.DishFlavor;
    import java.util.List;
    
    @Mapper
    public interface DishFlavorMapper {
        /**
         * 批量插入口味数据
         * @param flavors
         */
        void insertBatch(List<DishFlavor> flavors);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在/resources/mapper中创建DishFlavorMapper.xml

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="com.sky.mapper.DishFlavorMapper">
        <insert id="insertBatch">
            insert into dish_flavor (dish_id, name, value) VALUES
            <foreach collection="flavors" item="df" separator=",">
                (#{df.dishId},#{df.name},#{df.value})
            foreach>
        insert>
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.3 功能测试

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

    在这里插入图片描述

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

    dish表:

    在这里插入图片描述

    dish_flavor表:
    在这里插入图片描述

    测试成功。

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

  • 相关阅读:
    Windows10启用WSL2,安装子系统Ubuntu20.04.4 LTS并在Ubuntu中部署docker
    基于角度敏感的空间注意力机制的轻量型旋转目标检测器
    Qt入门总结
    036-JList列表控件使用案例讲解
    抖音seo矩阵系统源代码开发技术文档分享--SaaS开源
    Python【多分支选择结构】
    【感性认识】嵌入式开发有何不同
    第一百三十三回 StreamProvier
    Linux:无法接收组播数据
    @HttpMessageConverter注解的基本介绍
  • 原文地址:https://blog.csdn.net/m0_59230408/article/details/132889355