【一周学完苍穹外卖】DAY3新增菜品以及阿里云配置

目录

公共字段填充

问题分析

​编辑实现思路

枚举

注解(Anotation)

反射

创建名为class的对象

新增菜品

需求分析

文件上传接口

阿里云OSS

介绍

@Configuration

@Slf4j

@Bean

@ConditionalOnMissingBean

新增菜品接口

菜品分页查询


公共字段填充

问题分析

        先前新增员工和菜品时,列如创建时间,创建人id,修改时间等重复的字段代码比较冗余,不便于后期维护。

实现思路

产生契机:在Insert和update这两个操作时产生

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

楼主这一块不是很熟练,再总结一下

枚举

在Java中,枚举是一种具有固定的可能值集的类型。

enum Size { 
   SMALL, MEDIUM, LARGE, EXTRALARGE 
}

花括号内的值称为枚举值(常量)。这些是枚举类型可以保留的唯一值。

使用案例:

class Test {
 Size pizzaSize;
 public Test(Size pizzaSize) {
   this.pizzaSize = pizzaSize;
 }
 public void orderPizza() {
   switch(pizzaSize) {
     case SMALL:
       System.out.println("我点了一个小号比萨饼。");
       break;
     case MEDIUM:
       System.out.println("我点了一个中号的披萨。");
       break;
     default:
       System.out.println("我不知道该点哪一种。");
       break;
   }
 }
}

注解(Anotation)

任何声明都可以通过将其放在声明上面来标记注解。从Java 8开始,注释也可以放在类型之前。

//内置注解
@Override
@Deprecated
@SuppressWarnings
//元注解
@Retention//指定了该注解可用的最高级别。

@Documented//默认情况下,自定义注解不包含在官方Java文档中。要将注解包含在Javadoc文档中,请使用@Documented注解。

@Target(ElementType)//我们可以使用@Target注解将注解限制为应用于特定目标。

@Inherited//注解类型不能从超类继承。但是,如果需要将注解从超类继承到子类,则可以使用@Inherited注解。

@Repeatable
//带有@Repeatable标记的注解可以多次应用于同一声明。
//自定义注解
@interface MyCustomAnnotation {
  String value() default "default value";
}

class Main {
  @MyCustomAnnotation(value = "nhooo")//这里传入的value就是自定义注解里的"default value"
  public void method1() {
    System.out.println("测试方法1");
  }

  public static void main(String[] args) throws Exception {
    Main obj = new Main();
    obj.method1();
  }
}

如果需要检索注解数据,可以使用反射

反射

在学习Java反射之前,我们需要了解一个名为Class的Java类。

Java中有一个名为Class的类,该类在运行时保留有关对象和类的所有信息。

Class对象描述了特定类的属性。该对象用于执行反射。

创建名为class的对象
//使用forName()方法
Class Dog {  }
Class c1 = Class.forName("Dog");


//使用getClass()方法
Dog d1 = new Dog()
Class c1 = d1.getClass();

//使用.class
Class c1 = Dog.class;

自动填充公共字段

@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) throws NoSuchMethodException {
        log.info("开始进行公共字段自动填充。。。");
        //获取当前被拦截的方法上的数据库操作类型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
        OperationType operation = 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(operation == OperationType.INSERT) {
            try {
                Method setCreateTiME = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);
                setCreateTiME.invoke(entity, now);
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}

新增菜品

需求分析

文件上传接口

阿里云OSS

介绍

        阿里云对象存储OSS(Object Storage Service),是一款海量、安全、低成本、高可靠的云存储服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。

阿里云OSS将数据文件以对象(object)的形式上传到存储空间(bucket)中

楼主花了四块多买了一个oss,教程网上很多,就不赘述了

接下来是配置。在application.yml文件中

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}

再在application-dev.xml文件中附上具体的值

OssConfiguration类

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

    }
}

@Configuration

声明该类是一个 Spring 配置类,用于替代传统的 XML 配置文件,通过 Java 代码定义 Bean 及其依赖关系

@Slf4j

通过 Lombok 自动生成一个名为 log 的日志对象

@Bean

在配置类中声明一个 Bean,告诉 Spring 容器如何创建该 Bean 的实例。

@ConditionalOnMissingBean

条件化创建 Bean,仅当 Spring 容器中 不存在 该类型(或指定名称)的 Bean 时,才会执行当前 @Bean 方法。
public class CommonController {
    @Autowired
    private AliOssUtil aliOssUtil;

    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping("upload")
    public Result<String> upload(MultipartFile file) {
    log.info("文件上传:{}",file);
        try {
            //原始文件名
            String originalFilename = file.getOriginalFilename();
            //截取原始文件名的后缀
            String extention = originalFilename.substring(originalFilename.lastIndexOf("."));
            //构造新文件名称
            String objectName = UUID.randomUUID().toString() + extention;
            String filePath = aliOssUtil.upload(file.getBytes(), objectName);
            return Result.success(filePath);
        } catch (IOException e) {
           log.error("文件上传失败", e);
        }
        return Result.error("文件上传失败");
    }
}

@Autowired 的核心作用

自动装配 Bean

  • Spring 会在容器中查找与被注入字段 / 方法参数类型匹配的 Bean,并将其注入。
  • 例如,在 CommonController 中
@Autowired
private AliOssUtil aliOssUtil; // Spring 会自动查找 AliOssUtil 类型的 Bean 并注入

成功!

可以看到阿里OSS里也用相应的图片

新增菜品接口

Controller类

@RestController
@RequestMapping("/admin/dish")
@Api(tags="菜品管理")
@Slf4j
public class DishController {
    @Autowired
    private DishService dishService;

    @PostMapping
    @ApiOperation("新增菜品")
    public Result addDish(@RequestBody DishDTO dishDTO) {
        log.info("新增菜品:{}",dishDTO);
        dishService.saveWithFlavor(dishDTO);

        return Result.success();
    }
}

serviceImpl类

 public void saveWithFlavor(DishDTO dish) {
        Dish dishEntity = new Dish();
        BeanUtils.copyProperties(dish, dishEntity);

        //向菜品表插入一条数据
        dishMapper.insert(dishEntity);
        Long id =dishEntity.getId();
        //向口味表插入n条数据
        List<DishFlavor> flavors = dish.getFlavors();
        if(flavors!=null&&flavors.size()>0){

            flavors.forEach(flavor -> {
                flavor.setDishId(id);
            });

            //批量插入
            dishFlavorMapper.insertBatch(flavors);
        }

    }

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>

注意:useGeneratedKeys="true"和 keyProperty="id"配合,能够指定主键值要映射到的实体类属性(或字段

DishFlavorMapper.xml

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

注意:在 MyBatis 中,<foreach> 标签是 动态 SQL 的核心工具之一,主要用于 遍历集合数据并生成批量 SQL 语句

  • 集合参数的处理
    • 若方法参数是 单个集合(如 List<Flavor> flavors),collection 直接写集合变量名(flavors)。
    • 若参数是 多个集合或对象,需通过 @Param 注解为参数命名,避免 MyBatis 无法识别集合名。
  • SQL 拼接安全
    • 避免在 separator 中使用 ORAND 等可能导致 SQL 注入的符号(需结合业务逻辑严格校验)。
  • 数据库批量操作优化
    • 部分数据库(如 MySQL)支持 batch insert,配合 MyBatis 的 ExecutorType.BATCH 可进一步提升性能。

菜品分页查询

这个功能需要注意的时SQL中的联合查询语句

 <select id="pageQuery" resultType="com.sky.vo.DishVO">
        select  d.*,c.name 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 like concat('%',#{categoryId},'%')
            </if>
            <if test="status != null">
                and d.status like concat('%',#{status},'%')
            </if>

        </where>
        order by d.create_time desc
    </select>

删除菜品

业务逻辑

@Transactional
    public  void deleteDish(List<Long> ids){
        //判断是否能被删除
        for(Long id:ids){
            Dish dish=dishMapper.getById(id);
            if(dish.getStatus() == StatusConstant.ENABLE){
                //不允许删除
                throw new RuntimeException(MessageConstant.DISH_ON_SALE);
            }
        }

        //判断是否在套餐中

           List<Long> setmealIds=setmealDishMapper.getSetmealIdByIds(ids);
            if(setmealIds!=null && setmealIds.size()>0){
                //不允许删除
                throw new RuntimeException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
            }


        //删除菜品表中的数据
            for(Long id:ids){
                dishMapper.deleteById(id);
                //删除关联的口味表中的数据
                dishFlavorMapper.deleteByDishId(id);
            }
       



    }

修改菜品

楼主这里犯了一个错误,结果找bug找了半天,而且IDEA控制台也没有任何信息

  <update id="update">
        update dish
        <set>
            <if test="name!=null">name = #{name}, </if>
        </set>
        where id = #{id}
    </update>

这里没写where语句导致找bug找了半个小时,气死了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值