【学习笔记】java项目—苍穹外卖day03

news2024/11/22 21:42:04

文章目录

  • 苍穹外卖-day03
    • 课程内容
    • 1. 公共字段自动填充
      • 1.1 问题分析
      • 1.2 实现思路
      • 1.3 代码开发
        • 1.3.1 步骤一
        • 1.3.2 步骤二
        • 1.3.3 步骤三
      • 1.4 功能测试
      • 1.5 代码提交
    • 2. 新增菜品
      • 2.1 需求分析与设计
        • 2.1.1 产品原型
        • 2.1.2 接口设计
        • 2.1.3 表设计
      • 2.2 代码开发
        • 2.2.1 文件上传实现
        • 2.2.2 新增菜品实现
      • 2.3 功能测试
      • 2.4代码提交
    • 3. 菜品分页查询
      • 3.1 需求分析和设计
        • 3.1.1 产品原型
        • 3.1.2 接口设计
      • 3.2 代码开发
        • 3.2.1 设计DTO类
        • 3.2.2 设计VO类
        • 3.2.3 Controller层
        • 3.2.4 Service层接口
        • 3.2.5 Service层实现类
        • 3.2.6 Mapper层
      • 3.3 功能测试
        • 3.3.1 接口文档测试
        • 3.3.2 前后端联调测试
    • 4. 删除菜品
      • 4.1 需求分析和设计
        • 4.1.1 产品原型
        • 4.1.2 接口设计
        • 4.1.3 表设计
      • 4.2 代码开发
        • 4.1.2 Controller层
        • 4.2.2 Service层接口
        • 4.2.3 Service层实现类
        • 4.2.4 Mapper层
      • 4.3 功能测试
      • 4.4 代码提交
    • 5. 修改菜品
      • 5.1 需求分析和设计
        • 5.1.1 产品原型
        • 5.1.2 接口设计
      • 5.2 代码开发
        • 5.2.1 根据id查询菜品实现
        • 5.2.1 修改菜品实现
      • 5.3 功能测试
      • 5.4 代码提交
  • 课后作业
    • 完成菜品管理模块中的**菜品起售停售**功能
      • 1. 根据产品原型进行需求分析,分析出业务规则
      • 2. 设计 菜品起售停售 功能的接口
      • 3. 根据接口设计进行代码实现
        • 3.1 DishController
        • 3.2 DishService
        • 3.3 DishServiceImpl
        • 3.4 SetmealMapper
        • 3.5 SetmealMapper.xml
      • 4. 分别通过swagger接口文档和前后端联调进行功能测试


苍穹外卖-day03

课程内容

  • 公共字段自动填充
  • 新增菜品
  • 菜品分页查询
  • 删除菜品
  • 修改菜品

功能实现: 菜品管理

菜品管理效果图:

在这里插入图片描述

1. 公共字段自动填充

1.1 问题分析

在上一章节我们已经完成了后台系统的员工管理功能菜品分类功能的开发,在新增员工或者新增菜品分类时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工或者编辑菜品分类时需要设置修改时间、修改人等字段。这些字段属于公共字段,也就是也就是在我们的系统中很多表中都会有这些字段,如下:

序号字段名含义数据类型
1create_time创建时间datetime
2create_user创建人idbigint
3update_time修改时间datetime
4update_user修改人idbigint

而针对于这些字段,我们的赋值方式为:

1). 在新增数据时, 将createTime、updateTime 设置为当前时间, createUser、updateUser设置为当前登录用户ID。

2). 在更新数据时, 将updateTime 设置为当前时间, updateUser设置为当前登录用户ID。

目前,在我们的项目中处理这些字段都是在每一个业务方法中进行赋值操作,如下:

新增员工方法:

	/**
     * 新增员工
     *
     * @param employeeDTO
     */
    public void save(EmployeeDTO employeeDTO) {
        //.......................
		//
        //设置当前记录的创建时间和修改时间
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());

        //设置当前记录创建人id和修改人id
        employee.setCreateUser(BaseContext.getCurrentId());//目前写个假数据,后期修改
        employee.setUpdateUser(BaseContext.getCurrentId());
		///
        employeeMapper.insert(employee);
    }

编辑员工方法:

	/**
     * 编辑员工信息
     *
     * @param employeeDTO
     */
    public void update(EmployeeDTO employeeDTO) {
       //........................................
	   ///
        employee.setUpdateTime(LocalDateTime.now());
        employee.setUpdateUser(BaseContext.getCurrentId());
       ///

        employeeMapper.update(employee);
    }

新增菜品分类方法:

	/**
     * 新增分类
     * @param categoryDTO
     */
    public void save(CategoryDTO categoryDTO) {
       //....................................
       //
        //设置创建时间、修改时间、创建人、修改人
        category.setCreateTime(LocalDateTime.now());
        category.setUpdateTime(LocalDateTime.now());
        category.setCreateUser(BaseContext.getCurrentId());
        category.setUpdateUser(BaseContext.getCurrentId());
        ///

        categoryMapper.insert(category);
    }

修改菜品分类方法:

	/**
     * 修改分类
     * @param categoryDTO
     */
    public void update(CategoryDTO categoryDTO) {
        //....................................
        
		//
        //设置修改时间、修改人
        category.setUpdateTime(LocalDateTime.now());
        category.setUpdateUser(BaseContext.getCurrentId());
        //

        categoryMapper.update(category);
    }

如果都按照上述的操作方式来处理这些公共字段, 需要在每一个业务方法中进行操作, 编码相对冗余、繁琐,那能不能对于这些公共字段在某个地方统一处理,来简化开发呢?

答案是可以的,我们使用AOP切面编程,实现功能增强,来完成公共字段自动填充功能。

1.2 实现思路

在实现公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。在上述的问题分析中,我们提到有四个公共字段,需要在新增/更新中进行赋值操作, 具体情况如下:

序号字段名含义数据类型操作类型
1create_time创建时间datetimeinsert
2create_user创建人idbigintinsert
3update_time修改时间datetimeinsertupdate
4update_user修改人idbigintinsertupdate

实现步骤:

1). 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法

2). 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

3). 在 Mapper 的方法上加入 AutoFill 注解

若要实现上述步骤,需掌握以下知识(之前课程内容都学过)

技术点: 枚举、注解、AOP、反射

1.3 代码开发

按照上一小节分析的实现步骤依次实现,共三步。

1.3.1 步骤一

自定义注解 AutoFill

进入到sky-server模块,创建com.sky.annotation包。

在这里插入图片描述

package com.sky.annotation;

import com.sky.enumeration.OperationType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
 */
@Target(ElementType.METHOD)  //指定注解加载方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT
    OperationType value();
}

其中OperationType已在sky-common模块中定义

在这里插入图片描述

package com.sky.enumeration;

/**
 * 数据库操作类型
 */
public enum OperationType {

    /**
     * 更新操作
     */
    UPDATE,

    /**
     * 插入操作
     */
    INSERT
}
1.3.2 步骤二

自定义切面 AutoFillAspect

在sky-server模块,创建com.sky.aspect包。

在这里插入图片描述

package com.sky.aspect;

/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 切入点
     */
     //锁定包com.sky.mapper和注解AutoFill
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") 
    public void autoFillPointCut(){}

    /**
     * 前置通知,在通知中进行公共字段的赋值
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        /重要
        //可先进行调试,是否能进入该方法 提前在mapper方法添加AutoFill注解
        log.info("开始进行公共字段自动填充...");

    }
}

完善自定义切面 AutoFillAspect 的 autoFill 方法

package com.sky.aspect;

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 切入点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

    /**
     * 前置通知,在通知中进行公共字段的赋值
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        log.info("开始进行公共字段自动填充...");

        //获取到当前被拦截的方法上的数据库操作类型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
        OperationType operationType = autoFill.value();//获得数据库操作类型

        //获取到当前被拦截的方法的参数--实体对象
        Object[] args = joinPoint.getArgs();
        if(args == null || args.length == 0){
            return;
        }

        Object entity = args[0];

        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        //根据当前不同的操作类型,为对应的属性通过反射来赋值
        if(operationType == OperationType.INSERT){
            //为4个公共字段赋值
            try {
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通过反射为对象属性赋值
                setCreateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else if(operationType == OperationType.UPDATE){
            //为2个公共字段赋值
            try {
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通过反射为对象属性赋值
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
1.3.3 步骤三

在Mapper接口的方法上加入 AutoFill 注解

CategoryMapper为例,分别在新增和修改方法添加@AutoFill()注解,也需要EmployeeMapper做相同操作

在这里插入图片描述

package com.sky.mapper;

@Mapper
public interface CategoryMapper {
    /**
     * 插入数据
     * @param category
     */
    @Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
            " VALUES" +
            " (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
    @AutoFill(value = OperationType.INSERT)
    void insert(Category category);
    /**
     * 根据id修改分类
     * @param category
     */
    @AutoFill(value = OperationType.UPDATE)
    void update(Category category);

}

同时,将业务层为公共字段赋值的代码注释掉。

1). 将员工管理的新增和编辑方法中的公共字段赋值的代码注释。

在这里插入图片描述

在这里插入图片描述

2). 将菜品分类管理的新增和修改方法中的公共字段赋值的代码注释。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1.4 功能测试

新增菜品分类为例,进行测试

启动项目和Nginx

在这里插入图片描述

查看控制台

通过观察控制台输出的SQL来确定公共字段填充是否完成

在这里插入图片描述

查看表

category表中数据

在这里插入图片描述

其中create_time,update_time,create_user,update_user字段都已完成自动填充。

由于使用admin(id=1)用户登录进行菜品添加操作,故create_user,update_user都为1.

1.5 代码提交

点击提交:

在这里插入图片描述

提交过程中,出现提示:

在这里插入图片描述

继续push:

在这里插入图片描述

推送成功:

在这里插入图片描述

2. 新增菜品

2.1 需求分析与设计

2.1.1 产品原型

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

新增菜品原型:

在这里插入图片描述

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

业务规则:

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

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

接口设计:

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

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

1. 根据类型查询分类

在这里插入图片描述

2. 文件上传

在这里插入图片描述

3. 新增菜品

在这里插入图片描述

2.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服务进行文件存储。(前面课程已学习过阿里云OSS,不再赘述)

在这里插入图片描述

实现步骤:

1). 定义OSS相关配置

在sky-server模块

application-dev.yml

在这里插入图片描述

sky:
  alioss:
    endpoint: oss-cn-hangzhou.aliyuncs.com
    access-key-id: LTAI5tPeFLzsPPT8gG3LPW64
    access-key-secret: U6k1brOZ8gaOIXv3nXbulGTUzy6Pd7
    bucket-name: web-0223

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;

import com.sky.properties.AliOssProperties;
import com.sky.utils.AliOssUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置类,用于创建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;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;

@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;

import com.sky.constant.MessageConstant;
import com.sky.result.Result;
import com.sky.utils.AliOssUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
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;
import java.io.IOException;
import java.util.UUID;

/**
 * 通用接口
 */
@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);
    }
}
2.2.2 新增菜品实现

1). 设计DTO类

在sky-pojo模块中

在这里插入图片描述

package com.sky.dto;

import com.sky.entity.DishFlavor;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

@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<>();
}

2). Controller层

进入到sky-server模块

在这里插入图片描述

package com.sky.controller.admin;

import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Set;

/**
 * 菜品管理
 */
@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;


import com.sky.dto.DishDTO;
import com.sky.entity.Dish;
import com.sky.entity.DishFlavor;
import com.sky.mapper.DishMapper;
import com.sky.service.DishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@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);//后绪步骤实现
        }
    }

}

5). Mapper层

DishMapper.java中添加

在这里插入图片描述

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

在/resources/mapper中创建DishMapper.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.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>

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);

}

在/resources/mapper中创建DishFlavorMapper.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.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>

2.3 功能测试

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

在这里插入图片描述

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

dish表:

在这里插入图片描述

dish_flavor表:

在这里插入图片描述

测试成功。

2.4代码提交

在这里插入图片描述

后续步骤和上述功能代码提交一致,不再赘述。

3. 菜品分页查询

3.1 需求分析和设计

3.1.1 产品原型

系统中的菜品数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。

菜品分页原型:

在这里插入图片描述

在菜品列表展示时,除了菜品的基本信息(名称、售价、售卖状态、最后操作时间)外,还有两个字段略微特殊,第一个是图片字段 ,我们从数据库查询出来的仅仅是图片的名字,图片要想在表格中回显展示出来,就需要下载这个图片。第二个是菜品分类,这里展示的是分类名称,而不是分类ID,此时我们就需要根据菜品的分类ID,去分类表中查询分类信息,然后在页面展示。

业务规则:

  • 根据页码展示菜品信息
  • 每页展示10条数据
  • 分页查询时可以根据需要输入菜品名称、菜品分类、菜品状态进行查询
3.1.2 接口设计

根据上述原型图,设计出相应的接口。

在这里插入图片描述

3.2 代码开发

3.2.1 设计DTO类

根据菜品分页查询接口定义设计对应的DTO:

在sky-pojo模块中,已定义

在这里插入图片描述

package com.sky.dto;

import lombok.Data;
import java.io.Serializable;

@Data
public class DishPageQueryDTO implements Serializable {

    private int page;
    private int pageSize;
    private String name;
    private Integer categoryId; //分类id
    private Integer status; //状态 0表示禁用 1表示启用

}
3.2.2 设计VO类

根据菜品分页查询接口定义设计对应的VO:

在sky-pojo模块中,已定义

在这里插入图片描述

package com.sky.vo;

import com.sky.entity.DishFlavor;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DishVO 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 LocalDateTime updateTime;
    //分类名称
    private String categoryName;
    //菜品关联的口味
    private List<DishFlavor> flavors = new ArrayList<>();
}
3.2.3 Controller层

根据接口定义添加DishController的page分页查询方法:

在这里插入图片描述

	/**
     * 菜品分页查询
     *
     * @param dishPageQueryDTO
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("菜品分页查询")
    public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
        log.info("菜品分页查询:{}", dishPageQueryDTO);
        PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);//后绪步骤定义
        return Result.success(pageResult);
    }
3.2.4 Service层接口

在 DishService 中扩展分页查询方法:

在这里插入图片描述

	/**
     * 菜品分页查询
     *
     * @param dishPageQueryDTO
     * @return
     */
    PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO);
3.2.5 Service层实现类

在 DishServiceImpl 中实现分页查询方法:

在这里插入图片描述

	/**
     * 菜品分页查询
     *
     * @param dishPageQueryDTO
     * @return
     */
    public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
        PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
        Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);//后绪步骤实现
        return new PageResult(page.getTotal(), page.getResult());
    }
3.2.6 Mapper层

在 DishMapper 接口中声明 pageQuery 方法:

在这里插入图片描述

	/**
     * 菜品分页查询
     *
     * @param dishPageQueryDTO
     * @return
     */
    Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);

在 DishMapper.xml 中编写SQL:

在这里插入图片描述

<select id="pageQuery" resultType="com.sky.vo.DishVO">
        select d.* , c.name as categoryName from dish d left outer join category c on d.category_id = c.id
        <where>
            <if test="name != null">
                and d.name like concat('%',#{name},'%')
            </if>
            <if test="categoryId != null">
                and d.category_id = #{categoryId}
            </if>
            <if test="status != null">
                and d.status = #{status}
            </if>
        </where>
        order by d.create_time desc
</select>

3.3 功能测试

3.3.1 接口文档测试

启动服务: 访问http://localhost:8080/doc.html,进入菜品分页查询接口

注意: 使用admin用户登录重新获取token,防止token失效。

在这里插入图片描述

点击发送:

在这里插入图片描述

3.3.2 前后端联调测试

启动nginx,访问 http://localhost

点击菜品管理

在这里插入图片描述

数据成功查出。

4. 删除菜品

4.1 需求分析和设计

4.1.1 产品原型

在菜品列表页面,每个菜品后面对应的操作分别为修改删除停售,可通过删除功能完成对菜品及相关的数据进行删除。

删除菜品原型:

在这里插入图片描述

业务规则:

  • 可以一次删除一个菜品,也可以批量删除菜品
  • 起售中的菜品不能删除
  • 被套餐关联的菜品不能删除
  • 删除菜品后,关联的口味数据也需要删除掉
4.1.2 接口设计

根据上述原型图,设计出相应的接口。

在这里插入图片描述

**注意:**删除一个菜品和批量删除菜品共用一个接口,故ids可包含多个菜品id,之间用逗号分隔。

4.1.3 表设计

在进行删除菜品操作时,会涉及到以下三张表。

在这里插入图片描述

注意事项:

  • 在dish表中删除菜品基本数据时,同时,也要把关联在dish_flavor表中的数据一块删除。
  • setmeal_dish表为菜品和套餐关联的中间表。
  • 若删除的菜品数据关联着某个套餐,此时,删除失败。
  • 若要删除套餐关联的菜品数据,先解除两者关联,再对菜品进行删除。

4.2 代码开发

4.1.2 Controller层

根据删除菜品的接口定义在DishController中创建方法:

在这里插入图片描述

	/**
     * 菜品批量删除
     *
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation("菜品批量删除")
    public Result delete(@RequestParam List<Long> ids) {
        log.info("菜品批量删除:{}", ids);
        dishService.deleteBatch(ids);//后绪步骤实现
        return Result.success();
    }
4.2.2 Service层接口

在DishService接口中声明deleteBatch方法:

在这里插入图片描述

	/**
     * 菜品批量删除
     *
     * @param ids
     */
    void deleteBatch(List<Long> ids);
4.2.3 Service层实现类

在DishServiceImpl中实现deleteBatch方法:

在这里插入图片描述

    @Autowired
    private SetmealDishMapper setmealDishMapper;
	/**
     * 菜品批量删除
     *
     * @param ids
     */
    @Transactional//事务
    public void deleteBatch(List<Long> ids) {
        //判断当前菜品是否能够删除---是否存在起售中的菜品??
        for (Long id : ids) {
            Dish dish = dishMapper.getById(id);//后绪步骤实现
            if (dish.getStatus() == StatusConstant.ENABLE) {
                //当前菜品处于起售中,不能删除
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        }

        //判断当前菜品是否能够删除---是否被套餐关联了??
        List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);
        if (setmealIds != null && setmealIds.size() > 0) {
            //当前菜品被套餐关联了,不能删除
            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
        }

        //删除菜品表中的菜品数据
        for (Long id : ids) {
            dishMapper.deleteById(id);//后绪步骤实现
            //删除菜品关联的口味数据
            dishFlavorMapper.deleteByDishId(id);//后绪步骤实现
        }
    }
4.2.4 Mapper层

在DishMapper中声明getById方法,并配置SQL:

在这里插入图片描述

	/**
     * 根据主键查询菜品
     *
     * @param id
     * @return
     */
    @Select("select * from dish where id = #{id}")
    Dish getById(Long id);

创建SetmealDishMapper,声明getSetmealIdsByDishIds方法,并在xml文件中编写SQL:

在这里插入图片描述

package com.sky.mapper;

import com.sky.entity.SetmealDish;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;

@Mapper
public interface SetmealDishMapper {
    /**
     * 根据菜品id查询对应的套餐id
     *
     * @param dishIds
     * @return
     */
    //select setmeal_id from setmeal_dish where dish_id in (1,2,3,4)
    List<Long> getSetmealIdsByDishIds(List<Long> dishIds);
}

SetmealDishMapper.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.sky.mapper.SetmealDishMapper">
    <select id="getSetmealIdsByDishIds" resultType="java.lang.Long">
        select setmeal_id from setmeal_dish where dish_id in
        <foreach collection="dishIds" item="dishId" separator="," open="(" close=")">
            #{dishId}
        </foreach>
    </select>
</mapper>

在DishMapper.java中声明deleteById方法并配置SQL:

	/**
     * 根据主键删除菜品数据
     *
     * @param id
     */
    @Delete("delete from dish where id = #{id}")
    void deleteById(Long id);

在DishFlavorMapper中声明deleteByDishId方法并配置SQL:

    /**
     * 根据菜品id删除对应的口味数据
     * @param dishId
     */
    @Delete("delete from dish_flavor where dish_id = #{dishId}")
    void deleteByDishId(Long dishId);

4.3 功能测试

既可以通过Swagger接口文档进行测试,也可以通过前后端联调测试,接下来,我们直接使用前后端联调测试

进入到菜品列表查询页面

在这里插入图片描述

对测试菜品进行删除操作

在这里插入图片描述

同时,进到dish表和dish_flavor两个表查看测试菜品的相关数据都已被成功删除。

再次,删除状态为启售的菜品

在这里插入图片描述

点击批量删除

在这里插入图片描述

删除失败,因为起售中的菜品不能删除。

4.4 代码提交

在这里插入图片描述

后续步骤和上述功能代码提交一致,不再赘述。

5. 修改菜品

5.1 需求分析和设计

5.1.1 产品原型

在菜品管理列表页面点击修改按钮,跳转到修改菜品页面,在修改页面回显菜品相关信息并进行修改,最后点击保存按钮完成修改操作。

修改菜品原型:

在这里插入图片描述

5.1.2 接口设计

通过对上述原型图进行分析,该页面共涉及4个接口。

接口:

  • 根据id查询菜品
  • 根据类型查询分类(已实现)
  • 文件上传(已实现)
  • 修改菜品

我们只需要实现根据id查询菜品修改菜品两个接口,接下来,我们来重点分析这两个接口。

1). 根据id查询菜品

在这里插入图片描述

2). 修改菜品

在这里插入图片描述

在这里插入图片描述

注:因为是修改功能,请求方式可设置为PUT。

5.2 代码开发

5.2.1 根据id查询菜品实现

1). Controller层

根据id查询菜品的接口定义在DishController中创建方法:

在这里插入图片描述

    /**
     * 根据id查询菜品
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询菜品")
    public Result<DishVO> getById(@PathVariable Long id) {
        log.info("根据id查询菜品:{}", id);
        DishVO dishVO = dishService.getByIdWithFlavor(id);//后绪步骤实现
        return Result.success(dishVO);
    }

2). Service层接口

在DishService接口中声明getByIdWithFlavor方法:

在这里插入图片描述

	/**
     * 根据id查询菜品和对应的口味数据
     *
     * @param id
     * @return
     */
    DishVO getByIdWithFlavor(Long id);

3). Service层实现类

在DishServiceImpl中实现getByIdWithFlavor方法:

在这里插入图片描述

	/**
     * 根据id查询菜品和对应的口味数据
     *
     * @param id
     * @return
     */
    public DishVO getByIdWithFlavor(Long id) {
        //根据id查询菜品数据
        Dish dish = dishMapper.getById(id);

        //根据菜品id查询口味数据
        List<DishFlavor> dishFlavors = dishFlavorMapper.getByDishId(id);//后绪步骤实现

        //将查询到的数据封装到VO
        DishVO dishVO = new DishVO();
        BeanUtils.copyProperties(dish, dishVO);
        dishVO.setFlavors(dishFlavors);

        return dishVO;
    }

4). Mapper层

在DishFlavorMapper中声明getByDishId方法,并配置SQL:

在这里插入图片描述

    /**
     * 根据菜品id查询对应的口味数据
     * @param dishId
     * @return
     */
    @Select("select * from dish_flavor where dish_id = #{dishId}")
    List<DishFlavor> getByDishId(Long dishId);
5.2.1 修改菜品实现

1). Controller层

根据修改菜品的接口定义在DishController中创建方法:

在这里插入图片描述

	/**
     * 修改菜品
     *
     * @param dishDTO
     * @return
     */
    @PutMapping
    @ApiOperation("修改菜品")
    public Result update(@RequestBody DishDTO dishDTO) {
        log.info("修改菜品:{}", dishDTO);
        dishService.updateWithFlavor(dishDTO);
        return Result.success();
    }

2). Service层接口

在DishService接口中声明updateWithFlavor方法:

在这里插入图片描述

	/**
     * 根据id修改菜品基本信息和对应的口味信息
     *
     * @param dishDTO
     */
    void updateWithFlavor(DishDTO dishDTO);

3). Service层实现类

在DishServiceImpl中实现updateWithFlavor方法:

在这里插入图片描述

	/**
     * 根据id修改菜品基本信息和对应的口味信息
     *
     * @param dishDTO
     */
    public void updateWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);

        //修改菜品表基本信息
        dishMapper.update(dish);

        //删除原有的口味数据
        dishFlavorMapper.deleteByDishId(dishDTO.getId());

        //重新插入口味数据
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && flavors.size() > 0) {
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishDTO.getId());
            });
            //向口味表插入n条数据
            dishFlavorMapper.insertBatch(flavors);
        }
    }

4). Mapper层

在DishMapper中,声明update方法:

在这里插入图片描述

	/**
     * 根据id动态修改菜品数据
     *
     * @param dish
     */
    @AutoFill(value = OperationType.UPDATE)
    void update(Dish dish);

并在DishMapper.xml文件中编写SQL:

在这里插入图片描述

<update id="update">
        update dish
        <set>
            <if test="name != null">name = #{name},</if>
            <if test="categoryId != null">category_id = #{categoryId},</if>
            <if test="price != null">price = #{price},</if>
            <if test="image != null">image = #{image},</if>
            <if test="description != null">description = #{description},</if>
            <if test="status != null">status = #{status},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="updateUser != null">update_user = #{updateUser},</if>
        </set>
        where id = #{id}
</update>

5.3 功能测试

本次测试直接通过前后端联调测试 ,可使用Debug方式启动项目,观察运行中步骤。

进入菜品列表查询页面,对第一个菜品的价格进行修改

在这里插入图片描述

点击修改,回显成功

在这里插入图片描述

菜品价格修改后,点击保存

在这里插入图片描述

修改成功

5.4 代码提交

在这里插入图片描述

后续步骤和上述功能代码提交一致,不再赘述。


课后作业

完成菜品管理模块中的 菜品起售停售 功能,要求:

  1. 根据产品原型进行需求分析,分析出业务规则
  2. 设计 菜品起售停售 功能的接口
  3. 根据接口设计进行代码实现
  4. 分别通过swagger接口文档和前后端联调进行功能测试

完成菜品管理模块中的菜品起售停售功能

1. 根据产品原型进行需求分析,分析出业务规则

菜品起售表示该菜品可以对外售卖,在用户端可以点餐,菜品停售表示此菜品下架,用户端无法点餐。

业务规则为:如果执行停售操作,则包含此菜品的套餐也需要停售。

2. 设计 菜品起售停售 功能的接口

在这里插入图片描述

3. 根据接口设计进行代码实现

3.1 DishController

在这里插入图片描述

/**
     * 菜品起售停售
     * @param status
     * @param id
     * @return
*/
@PostMapping("/status/{status}")
@ApiOperation("菜品起售停售")
public Result<String> startOrStop(@PathVariable Integer status, Long id){
    dishService.startOrStop(status,id);
    return Result.success();
}
3.2 DishService

在这里插入图片描述

/**
     * 菜品起售停售
     * @param status
     * @param id
*/
void startOrStop(Integer status, Long id);
3.3 DishServiceImpl

在这里插入图片描述

/**
     * 菜品起售停售
     *
     * @param status
     * @param id
*/
@Transactional
public void startOrStop(Integer status, Long id) {
    Dish dish = Dish.builder()
        .id(id)
        .status(status)
        .build();
    dishMapper.update(dish);

    if (status == StatusConstant.DISABLE) {
        // 如果是停售操作,还需要将包含当前菜品的套餐也停售
        List<Long> dishIds = new ArrayList<>();
        dishIds.add(id);
        // select setmeal_id from setmeal_dish where dish_id in (?,?,?)
        List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(dishIds);
        if (setmealIds != null && setmealIds.size() > 0) {
            for (Long setmealId : setmealIds) {
                Setmeal setmeal = Setmeal.builder()
                    .id(setmealId)
                    .status(StatusConstant.DISABLE)
                    .build();
                setmealMapper.update(setmeal);
            }
        }
    }
}
3.4 SetmealMapper

在这里插入图片描述

/**
     * 根据id修改套餐
     *
     * @param setmeal
 */
@AutoFill(OperationType.UPDATE)
void update(Setmeal setmeal);
3.5 SetmealMapper.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.sky.mapper.SetmealMapper">

    <update id="update" parameterType="Setmeal">
        update setmeal
        <set>
            <if test="name != null">
                name = #{name},
            </if>
            <if test="categoryId != null">
                category_id = #{categoryId},
            </if>
            <if test="price != null">
                price = #{price},
            </if>
            <if test="status != null">
                status = #{status},
            </if>
            <if test="description != null">
                description = #{description},
            </if>
            <if test="image != null">
                image = #{image},
            </if>
            <if test="updateTime != null">
                update_time = #{updateTime},
            </if>
            <if test="updateUser != null">
                update_user = #{updateUser}
            </if>
        </set>
        where id = #{id}
    </update>

</mapper>

4. 分别通过swagger接口文档和前后端联调进行功能测试

在这里插入图片描述

在这里插入图片描述


资料来源:b站黑马程序员


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1566463.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【代码随想录】数组

704. 二分查找 class Solution {public int search(int[] nums, int target) {if(numsnull || nums.length0){return -1;}return searchTarget(nums, 0, nums.length-1, target);}private int searchTarget(int[] nums, int left, int right, int target){//left和right都指向同…

RegionCLIP网络结构解析 Region-based Language-Image Pretraining

1、简单介绍 主要是关注目标检测方面的工作&#xff0c;现在纯CV已经前景黯淡&#xff0c;即使前段时间的YOLOv9发布也是关注一般。 现在大模型已成热点&#xff0c;而大模型要求的数据量和算力和算法复杂度&#xff0c;显然让很多人却步。但是具有大模型特点的多模态算法也算…

Vue3从入门到实战:路由的query和params参数

在Vue 3中&#xff0c;我们可以通过路由的查询参数来传递数据。这意味着我们可以在不同的页面之间传递一些信息&#xff0c;以便页面可以根据这些信息来显示不同的内容或执行不同的操作。 查询参数的使用方式类似于在URL中添加附加信息&#xff0c;以便页面之间可以根据这些信息…

AI技术创业:把握机遇,提升能力,迎接未来挑战

文章目录 人工智能三次浪潮人工智能时代机遇提升核心能力AI时代的长期赛道和早期优势实践应用&#xff1a;让AI工具为你所用学会变通&#xff1a;适应AI领域的快速变化提升核心能力&#xff1a;想象力、创造力和提问能力结语 人工智能三次浪潮 第一次黄金期&#xff1a;1956年…

基于SSM的网络视频播放器

目录 背景 技术简介 系统简介 界面预览 背景 互联网的迅猛发展彻底转变了全球各类组织的管理策略。自20世纪90年代起&#xff0c;中国政府和企业便开始探索利用网络系统进行信息管理。然而&#xff0c;早期的网络覆盖不广泛、用户接受度不高、相关法律法规不完善以及技术开…

WPF文本框TextEdit不以科学计数法显示

WPF文本框TextEdit不以科学计数法显示 一个float或者double类型的数值&#xff0c;如果小数点后0的个数≥4&#xff0c;在界面上就会自动以科学计数法显示&#xff0c; 比如&#xff1a;0.00003会显示成这样 但是很多时候我并不希望它这样显示&#xff0c;因为这样不方便编辑…

成都欣丰洪泰文化传媒有限公司引领电商新风向

在当今数字化时代&#xff0c;电子商务行业日新月异&#xff0c;竞争激烈。然而&#xff0c;在这股浪潮中&#xff0c;成都欣丰洪泰文化传媒有限公司凭借其独特的战略眼光和创新精神&#xff0c;正引领着电商领域的新浪潮。本文将探讨成都欣丰洪泰文化传媒有限公司如何在激烈的…

实战webSocket压测(二)jmeter配置webSocket连接

背景 我们可以通过Jmeter添加插件实现webSocket脚本编写。WebSocket的插件较多&#xff0c;我选择以WebSocket Samplers by Peter Doornbosch为例来进行配置。 步骤1、WebSocket Samplers插件安装 下载地址&#xff1a;JMeter WebSocket Samplers&#xff0c;建议下载最新版本…

阿里巴巴25届实习生内推

#阿里巴巴 #春招实习 阿里国际春季2025届实习生招聘4月1日已正式启动&#xff01;学生网申投递、师兄师姐内推通道均已开放 整体介绍&#xff08;含在招岗位&#xff09; 内推投递方式 方式一&#xff1a;内推码自行投递 方式二&#xff1a;通过简历投递 简历发邮箱&#xf…

Lumos学习王佩丰Excel第一讲:认识Excel

最近发现自己在操作excel的一些特殊功能时会有些不顺手&#xff0c;所以索性找了一个比较全的教程&#xff08;王佩丰excel24讲&#xff09;拿来学习&#xff0c;刚好形成文档笔记&#xff0c;分享给有需要但没有时间看视频的朋友们。整体笔记以王老师授课的知识点去记录&#…

蓝桥杯备考

目录 P8823 [传智杯 #3 初赛] 期末考试成绩 题目描述 输入格式 输出格式 输入输出样例 说明/提示 代码 P8828 [传智杯 #3 练习赛] 直角三角形 题目描述 输入格式 输出格式 输入输出样例 代码 P8833 [传智杯 #3 决赛] 课程 题目背景 题目描述 输入格式 输出格式…

【热门话题】文言一心与ChatGPT-4:一场跨时代智能对话系统的深度比较

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 文言一心与ChatGPT-4&#xff1a;一场跨时代智能对话系统的深度比较一、技术背景…

西电计科大三下SOC微体系结构设计作业合集

目录 一.VHDL设计作业 1.基于硬件描述语言的3-8译码器逻辑电路设计 2.8位双向移位寄存器设计 3.基于有限状态机的自助售票系统设计 4.按键消抖电路设计 5.同步环形FIFO设计 6.线上实验——时钟模块设计 7.线上实验——原码二位乘法器设计 8.线上实验——布斯乘法器设…

新产品机会的两大来源:分析当前产品组合与创意生成工具或创造性思维技术

一、引言 在快速变化的商业世界中&#xff0c;企业/组织若想保持竞争力并持续繁荣&#xff0c;就必须不断寻找新产品机会。这些机会并非凭空而来&#xff0c;而是源于&#xff1a;1. 分析当前产品组合&#xff0c;找出可以进行产品改进或产品线延伸的领域。2.创意生成工具或创…

【JAVA】基础学习03变量和关键字

文章目录 JAVA变量与运算符1.关键字&#xff08;keyword&#xff09;2.标识符( identifier)2.1命名规则2.2命名规范2.3变量作用和类型2.3.1整型变量2.3.2补充&#xff1a;计算机存储单位2.3.3浮点类型&#xff1a;float、double2.3.4 关于浮点型精度的说明2.3.5 字符类型&#…

docker部署python

1.部署python 1.1安装docker&#xff08;按这个操作就可以&#xff09; http://t.csdnimg.cn/cezmt 1.2拉取python镜像&#xff0c;一般拉取收藏量最高的那个 sudo docker search python 1.3拉取python镜像 #可以设置版本号&#xff0c;也可以不设置版本号&#xff0c;不…

外卖配送时间预测项目

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 项目背景 外卖服务的兴起: 随着互联网技术和移动应用的发展&#xff0c;外卖成为一种日益普及的餐饮服务方式。顾客通过餐厅、杂货店的网站或移…

全球范围内2nm晶圆厂建设加速

随着人工智能浪潮席卷而来&#xff0c;先进制程芯片的重要性日益凸显。当前&#xff0c;3nm工艺节点是行业内最先进的节点。与此同时&#xff0c;台积电、三星、英特尔、Rapidus等厂商正积极布局建设2nm晶圆厂。台积电与三星此前计划于2025年量产2nm芯片&#xff0c;而Rapidus则…

嵌入式中常见的面试题分享

1.关键字static的作用是什么&#xff1f;为什么static变量只初始化一次&#xff1f; 1&#xff09;修饰局部变量&#xff1a;使得变量变成静态变量&#xff0c;存储在静态区&#xff0c;存储在静态区的数据周期和程序相同&#xff0c; 在main函数开始前初始化&#xff0c;在退…

java:6 数组(3)

文章目录 14. 二维数组14.1 定义14.2 二维数组的使用14.3 练习 【老韩视频p175-】 14. 二维数组 14.1 定义 多维数组我们只介绍二维数组&#xff1a; 二维数组的应用场景&#xff1a;比如我们开发一个五子棋游戏&#xff0c;棋盘就是需要二维数组来表示。请用二维数组输出如下…