苍穹外卖day3(1) 菜品管理(AOP、自定义注解、反射、文件上传)


前言

菜品管理,这部分包括公共字段自动填充、新增菜品。
公共字段自动填充在这部分主要使用到的技术有AOP切面编程,这里面又涉及到了反射机制,还有怎么去自定义注解之类的。
新增菜品这块新学的知识就是文件上传,这里用了阿里云OSS,把文件上传到云端存储,然后就是涉及到两个表之间的关联,怎么从dish_flavor表去获得另dish表中的dishId。


一、公共字段填充(枚举、自定义注解、AOP、反射)

1、问题分析

在为业务表中的公共字段设值时出现代码冗余,不利于后期维护。代码如下:

//设置当前记录的创建时间、修改时间、创建人、修改人
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());

//设置创建时间、修改时间、创建人、修改人
category.setCreateTime(LocalDateTime.now());
category.setUpdateTime(LocalDateTime.now());
category.setCreateUser(BaseContext.getCurrentId());
category.setUpdateUser(BaseContext.getCurrentId());

2、实现思路

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

@Target(ElementType.METHOD) //说明Annotation所修饰的对象范围
@Retention(RetentionPolicy.RUNTIME)  //定义该Annotation生命周期(编译/运行)
public @interface AutoFill {
    //枚举类OperationType,记录数据库操作类型:UPDATE INSERT
    OperationType value();  
}

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

@Aspect  //定义这个类为切面类
@Component  //把切面类放到容器中
@Slf4j
public class AutoFillAspect {
    // 具体切入点 告诉Spring当前通知方法要套用到哪个目标方法上
    @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 time = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();
        //根据当前不同的操作类型,为对应的属性通过反射来赋值
        if(operationType == OperationType.INSERT){
            //为四个公共字段赋值
           try {
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                //通过反射为对象属性赋值
                setCreateTime.invoke(entity,time);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,time);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                log.error("公共字段自动填充失败:{}", e.getMessage());
            }
        }else if(operationType == OperationType.UPDATE){
            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,time);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                log.error("公共字段自动填充失败:{}", e.getMessage());
            }
        }
    }
}

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

  @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);
  
 @AutoFill(value = OperationType.UPDATE)
    void update(Category category);

二、新增菜品

1、产品原型

在这里插入图片描述
业务规则
菜品名称必须是唯一的
菜品必须属于某个分类下,不能单独存在
新增菜品时可以根据情况选择菜品的口味
每个菜品必须对应张图片

2、接口设计

1、根据类型查询分类(第二天的分类管理部分已完成 public Result<List> queryByType(Integer type))
2、文件上传
在这里插入图片描述
3、新增菜品
在这里插入图片描述

3、数据库设计

这里涉及的表有两个:dish菜品表和dish_flavor口味表
在这里插入图片描述

4、代码开发

4.1 文件上传(阿里云OSS)

不理解的可以看这个视频,讲解很详细。
文件上传
1、开发文件上传接口
相关配置文件

#application-dev.yml
#这里可以自己在阿里云对象存储OSS中去创建一个Bucket,就能将图片上传到云端。
 alioss:
     endpoint: oss-cn-beijing.aliyuncs.com
     access-key-id: LTAI5tPeFLzsPPT8gG3LPW64
     access-key-secret: U6k1brOZ8gaOIXv3nXbulGTUzy6Pd7
     bucket-name: sky-itcast

#application.yml
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}

配置类 用于创建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());
    }
}

通用方法

@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();
            //截取原始文件名的后缀  1.jpg
            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);
    }
}

4.2 新增菜品

1、根据接口请求数据设计了实体类DishDTO
2、controller层

@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {
    @Autowired
    private DishService dishService;    
    @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO){
        log.info("新增菜品{}",dishDTO);
        //除了关联菜品表,同时也要把口味信息保存
        dishService.saveWithFlavor(dishDTO);
        return Result.success();
    }
}

3、在DishService声明aveWithFlavor方法(新增菜品和对应的口味),在DishServiceImpl实现方法

    @Transactional  //涉及多个表的操作,要保证数据一致性,所以使用事务注解,保证方法的原子性
    @Override
    public void saveWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO,dish);  //因为dishDTO还包含口味数据,而我们只需要插入菜品
        //1、向菜品表插入1条数据,一次只能插入一条
        dishMapper.insert(dish);
        //2、向口味表插入n条数据
        //获取insert语句生成的主键值
        Long dishId = dish.getId(); //口味的dishId属性要从菜品dish表中获取
        //拿到封装在dishDTO中口味的信息
        List<DishFlavor> flavors = dishDTO.getFlavors();
        //判断集合中有没有数据
        if(flavors != null && flavors.size() > 0){
        //遍历flavors集合,每次遍历就设置dishId的值
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });
            //批量插入
            dishFlavorMapper.insertBatch(flavors);
        }
    }

4、在DishMapper接口中写insert方法

@AutoFill(OperationType.INSERT)  //update_time create_time update_user create_user自动填充
void insert(Dish dish);

在DishMapper.xml文件中写sql语句

<!--口味的dishId属性要从菜品dish表中获取,
所以要将数据库表中的id值赋给dish实体类中的Id值,然后再赋给口味表dish_flavor中的dishId
useGeneratedKeys:true 获取主键值    
keyProperty:id 将主键值赋给id属性-->
 <insert id="insert" useGeneratedKeys="true" keyProperty="id">
       insert into sky_take_out.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>

5、在DishFlavorMapper接口写insertBatch方法,批量插入口味数据

void insertBatch(List<DishFlavor> flavors);

在DishFlavorMapper.xml文件中写sql语句

   <insert id="insertBatch">
	   insert into sky_take_out.dish_flavor(dish_id, name, value) values
	   <!--遍历lavors集合中的元素(dishFlavor对象),没遍历一次就用,分隔-->
	    <foreach collection="flavors" item="df" separator=",">
	        <!--dishId要从dish表中获取-->
		    (#{df.dishId},#{df.name},#{df.value})
	    </foreach>
   </insert>

  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值