二刷苍穹外卖 day03

发布于:2025-06-23 ⋅ 阅读:(17) ⋅ 点赞:(0)

公共字段自动填充

通过AOP切面编程,实现功能增强,完成对公共字段的自动填充

实现思路:对以下四个字段进行统一处理
![[Pasted image 20250613222608.png]]

实现步骤:
1).自定义注解AutoFill,用于表示需要进行公共字段自动填充的方法
2)自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值
3).在Mapper的方法上加入AutoFill注解

代码实现

自定义注解AutoFill

@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface AutoFill {  
    OperationType value();  
}

@Target(ElementType.MethOD)表明这个注解只能应用在方法上
@Retention(RetentionPolicy.RUNTIME)表明该注解在运行时依然存在,可通过反射获取
OperationType value();定义了该注解有一个名为value的属性,类型为OperationType ,使用该注解时需为value属性赋值。

自定义切面AutoFillAspect

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

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

    }

execution(* com.sky.mapper.*.*(..))表示匹配com.sky.mapper包下所有类的所有方法;&& @annotation(com.sky.annotation.AutoFill)表示被com.sky.annotation.AutoFill注解标记的方法才会被匹配。整体意思是,当com.sky.mapper包下的方法被AutoFill注解标记时,该切入点匹配。

  • public void autoFillPointCut() { } 是切入点方法,它本身没有实际的方法体,只是作为切入点的标识。

总结:

AutoFill是“标记”,而AutoFillAspect是"实现逻辑"
AutoFill用于标记目标方法(@Target(ElementType.METHOD)声明注解只能标注在方法上)
通过OperationType value()声明注解需要携带一个OperationType类型的参数
AutoFillAspect是一个切面类:
通过AOP的切点@Pointcut筛选出对应包下能被@AutoFill注解标记的方法
通过注解的操作类型自动填充字段

通过这种方式,只需要在Mapper中确定是插入操作还是更新操作即可统一管理公共字段的赋值逻辑,开发者只需在 Mapper 方法上声明 @AutoFill(OperationType.INSERT)@AutoFill(OperationType.UPDATE),切面会自动完成公共字段的赋值,无需手动编写重复代码。

AOP

面向切面编程,在Spring AOP中可以认为是面向方法编程
在不修改目标方法源代码的前提下,底层通过动态代理机制实现对目标方法的编程,动态代理是目前面向方法编程最主流的实现技术
![[Pasted image 20250622192431.png]]

文件上传

定义OSS相关配置

在这里插入图片描述

读取OSS配置

![[Pasted image 20250622194454.png]]

  • @ConfigurationProperties(prefix = "sky.alioss"):这是 Spring Boot 提供的注解,用于将配置文件中以sky.alioss为前缀的属性值绑定到该类的对应字段上。比如在application.propertiesapplication.yml等配置文件中,sky.alioss.endpoint的值会被绑定到类中的endpoint字段。

生成OSS工具类对象

![[Pasted image 20250622194610.png]]

定义文件上传接口

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;  
  
    @PostMapping("/upload")  
    @ApiOperation("文件上传")  
    public Result<String> upload(MultipartFile file) {  
        log.info("文件上传:{}", file);  
        try {  
            String originalFilename = file.getOriginalFilename();//原始文件名  
            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. String originalFilename = file.getOriginalFilename();:获取文件的原始文件名,并将其存储在originalFilename变量中。
  2. String extension = originalFilename.substring(originalFilename.lastIndexOf("."));:通过substring方法从原始文件名中截取文件后缀。lastIndexOf(".")用于找到文件名中最后一个点号(.)的位置,以此来确定后缀的起始位置,截取的后缀存储在extension变量中。
  3. String objectName = UUID.randomUUID().toString() + extension;:生成一个唯一的通用唯一识别码(UUID),并将其与文件后缀拼接起来,作为上传文件的名称存储在objectName变量中。这样做可以确保上传文件的名称具有唯一性,避免文件名冲突。
  4. String filePath = aliOssUtil.upload(file.getBytes(), objectName);:调用aliOssUtil工具类中的upload方法,将文件的字节数组(file.getBytes())和生成的上传文件名(objectName)作为参数传入,该方法返回文件上传后的请求路径,并存储在filePath变量中。这一步实现了将文件上传到指定的存储位置(可能是阿里云对象存储 OSS,由aliOssUtil工具类决定)。

新增菜品

service层

 @Override  
 @Transactional
    public void saveWithFlavor(DishDTO dishDTO) {  
        Dish dish = new Dish();  
        BeanUtils.copyProperties(dishDTO, dish);  
  
        dishMapper.insert(dish);  
  
        Long dishId = dish.getId();  
  
        List<DishFlavor> flavors = dishDTO.getFlavors();  
        if (flavors != null && flavors.size() > 0) {  
            /**  
             * 如果直接调用 flavors.size() 而不先检查 flavors 是否为 null,  
             * 当 flavors 是 null 时会抛出 NullPointerException(空指针异常),  
             * 导致程序崩溃。  
             */  
//            for (DishFlavor flavor : flavors) {  
//                flavor.setDishId(dishId);  
//            }  
            flavors.forEach(dishFlavor -> {  
                dishFlavor.setDishId(dishId);  
            });  
            dishFlavorMapper.insertBatch(flavors);  
        }  
  
  
    }

Mapper

<?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” 表示使用数据库自动生成的主键值。当插入数据时,如果数据库支持自动生成主键(如 MySQL 的自增主键),设置此属性为 true 后,MyBatis 会在插入操作执行完毕后,获取数据库生成的主键值。
keyProperty=“id” 表示将获取到的自动生成的主键值,赋值给 Java 对象中的 id 属性。也就是说,假设在 Java 代码中有一个 JavaBean 对象,其中包含 id 这个属性,插入操作完成后,数据库生成的主键值会自动填充到该 JavaBean 对象的 id 属性中。

菜品分页查询

service层

	/**
     * 菜品分页查询
     *
     * @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());
    }

page.getResult()实际上返回的是List集合,DishVO是单个菜品数据的 “数据模板”,而page.getResult()是当前页所有菜品数据的 “容器”(列表),两者共同完成了 “分页查询结果的结构化封装”。

删除菜品

Service

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

逻辑:查询所有菜品,是否存在启售?没有启售的再去查询这些菜品有没有被套餐关联,没有才能逐个删除

Mapper

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

resultType="java.lang.Long" 定义了单条记录的映射类型,而 MyBatis 会自动将多条记录的Long对象封装为List<Long>,因此接口方法返回List<Long>是合理且 MyBatis 支持的

修改菜品

service

	/**
     * 根据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);
        }
    }

先删除原有口味
再插入口味数据

总结

本节主要涉及公共字段自动填充、文件上传以及联表增删改查功能

1.AOP切面编程是什么?
在Spring AOP中可视作面向方法编程

2.@AutoFill的作用是什么?
自定义注解,用于标记需要填充公共字段的Mapper方法,通过在方法上标注并制定操作类型,AOP 切面会拦截并自动完成字段赋值,避免重复代码的书写

3.AOP切面AutoFillAspect如何识别需要自动填充的方法
通过@Pointcut定义切入点表达式:execution(* com.sky.mapper.*.*(..)) && @annotation(AutoFill),表示拦截com.sky.mapper包下所有被@AutoFill注解标记的方法,从而触发公共字段填充逻辑。

4.useGeneratedKeys="true"的作用
用于获取数据库自动生成的主键,设置后,MyBatis会在插入操作完成后将生成的主键赋值给keyProperty制定的对象方便后续使用

5.OSS 配置类如何与 application.yml 中的参数绑定?
通过@ConfigurationProperties(prefix = "sky.alioss")注解,Spring 会自动将配置文件中以sky.alioss为前缀的参数(如endpointaccessKeyId)绑定到配置类的对应字段上。

6.在pageQuery分页查询方法中,Page和page.getResult()是什么含义?
page.getResult()返回的实际上是List集合,DishVo是单个菜品数据,而page.getResult()会返回当前页所有菜品数据以容器(list)的形式实现


网站公告

今日签到

点亮在社区的每一天
去签到