苍穹外卖学习 Day03

前言

该篇博客用于记录苍穹外卖Day03的学习,内容包括公共字段自动填充、菜品管理的开发
注意:可以根据代码上的注释加以理解,很多思路我都用注释写出来了

公共字段填充

在这里插入图片描述

如图所示,无论是员工管理、分类管理,还是后面我们要实现的菜品管理等,都有create_time、create_user、update_time、update_user这几个公共字段,每次都要在service层重复编写相同的代码来将这些值设置进去,易造成代码的冗余,而且当数据发生变更时,要一个一个修改,也不易于维护。所以这些公共字段我们要实现它的自动填充。

实现方法:

  • 自定义注解AutoFill,用于表示需要进行公共字段自动填充的方法(Insert Update)
//自定义注解,用于表示某个方法需要进行公共字段自动填充功能
@Target(ElementType.METHOD)//作用在方法上
@Retention(RetentionPolicy.RUNTIME)//设置生命周期->运行时使用
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT
    OperationType value();
}
  • 自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值
//自定义切面类,实现公共字段自动填充的逻辑
@Aspect
@Slf4j
@Component
public class AutoFillAspect {

    //切入点,定义作用范围
    //匹配拦截mapper包下的所以类所有方法中 && 加了AutoFill注解的方法
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){

    }

    //通过反射进行公共字段赋值
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        log.info("开始进行公共字段自动填充");

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

        //2.获取当前被拦截的方法的参数--实体对象args
        Object[] args= joinPoint.getArgs();
        if(args==null||args.length==0){
            return;
        }
        //因为都是默认将要操作的实体对象放在第一个,所以是args[0]
        Object entity=args[0];

        //3.准备赋值的数据
        LocalDateTime now = LocalDateTime.now();//当前时间
        Long currentId = BaseContext.getCurrentId();当前操作人id

        //4.根据当前不同的操作类型,为对应的属性通过反射来赋值
        if(operationType==OperationType.INSERT){
            //为四个公共字段赋值
            try {
                //获取setCreateTime()、setCreateUser()、setUpdateTime()、setUpdateUser()方法
                /*
                * AutoFillConstant.SET_CREATE_TIME-> "setCreateTime"
                AutoFillConstant.SET_CREATE_USER->"setCreateUser"
                AutoFillConstant.SET_UPDATE_TIME->"setUpdateTime"
                AutoFillConstant.SET_UPDATE_USER->"setUpdateUser"
                * */
                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) {
            //为两个公共字段赋值
            try {
                //获取setUpdateTime()、setUpdateUser()方法
                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();
            }
        }
    }
}
  • 在mapper层的方法上加上AutoFill(插入语句即第一个注解,更新语句即第二个注解)
@AutoFill(value = OperationType.INSERT)
@AutoFill(value = OperationType.UPDATE)

这样,那些公共属性就不需要我们自己再去手动赋值了

注意:自动填充只是不需要我们在service层手动去设置值,mapper层的SQL语句还是要写将自动填充的值保存到数据库中的逻辑的。相当于我们只是获得了要自动填充的值,这个值我们还是要手动存到数据库的。

菜品管理

文件上传阿里云

要使用阿里云对象存储服务,必须先在application.yml中配置好相关配置,如下所示:

(具体的配置信息我们配置在application-dev-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}

这里的access-key-id、access-key-secret是要你要去阿里云OSS服务申请一个AccessKey(记得保存下来),然后就可以获得access-key-id、access-key-secret的值,接下来你就可以去创建一个Bucket,创建时名称可以随便取,读写权限建议设置为公共读,bucket-name就是你创建Bucket时的名称,endpoint是根据你的创建Bucket时的地域决定的(在你的Bucket的概览中即可找到,如下)。

在这里插入图片描述

注意:AccessKey我们只需要申请一次,再创建新的项目时需要申请新的Bucket,access-key-id、access-key-secret用原来申请的AccessKey的就好。


下面来分析一下上传文件的具体过程

  • 在application.yml中先配置好配置项,具体的配置信息写在application-dev.yml中

  • 接着在AliOssProperties这个类中通过@ConfigurationProperties(prefix = “sky.alioss”)这个注解将配置文件中配置好的四个配置信息内容注入到AliOssProperties类中对应的四个属性内

  • 在工具类AliOssUtils中有四个属性对应着配置信息但这四个属性暂时没有内容

  • 我们就需要通过配置类(OssConfiguration)的方式将AliOssUtils的四个空的属性赋上值(因为AliOssProperties中有这四个属性的值,我们调用AliOssProperties的get方法将值赋给AliOssUtils创建出AliOssUtils对象)并注入到IOC容器内

  • 最后我们就可以在controller层注入AliOssUtils对象并调用它的upload()方法上传文件了

@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {

    @Autowired
    private AliOssUtil aliOssUtil;
    @PostMapping("/upload")
    @ApiOperation("文件上传")
    //MultipartFile file中因为前端提交的叫做file,所以这里的参数名是file
    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);
    }
}

注意:可能会遇到图片不显示的问题,问题可以能是return了错误的结果,或者bucket设置为了私有的。

新增菜品接口

在新增菜品接口,涉及到一个多表的插入操作。在新增一个菜品的时候,由于每个菜品有不同的口味,所以在该接口,我们会插入一条菜品数据以及多条这个菜品的口味数据。

重点分析一下实现类DishServiceImpl中的代码:

	//新增菜品和对应的口味
    @Transactional//事务注解,菜品和口味插入要么全部成功要么全部失败
    public void saveWithFlavor(DishDTO dishDTO) {
        Dish dish=new Dish();
        //将dishDto的属性赋给dish
        BeanUtils.copyProperties(dishDTO,dish);

        //向菜品表插1条数据
        dishMapper.insert(dish);

      //获取insert语句生成的主键值(获取菜品id)。因为是刚插入的数据,直接这样获取不到,需要在XML映射文件的SQL语句中做一些操作
        Long dishId=dish.getId();

        List<DishFlavor> flavors = dishDTO.getFlavors();
        if(flavors!=null&&flavors.size()>0){
            for (DishFlavor flavor : flavors) {
                //设置口味关联的菜品的id
                flavor.setDishId(dishId);
            }
            //向口味表插多条数据
            dishFlavorMapper.insertBatch(flavors);
        }
    }
  • @Transactional注解表示我们插入菜品数据和口味数据的操作要么同时成功,要么同时失败
  • 其次在成功插入菜品之后,我们需要获得插入的菜品的id(dish.getId()),将这个id设置进口味,让该菜品与其口味关联起来,但这个方法需要在插入菜品后将这个id返回,我们才能通过dish.getId()获取到这个id,我们需要在SQL语句中加入以下才能成功将id返回
	<insert id="insert" useGeneratedKeys="true" keyProperty="id">
       ...
    </insert>
useGeneratedKeys="true"表示我们需要获得插入这条数据后生成的主键值(id),keyProperty="id"表示将这个主键值赋给id这个属性
  • 最后我们通过遍历的方式将菜品id设置进口味,然后将多条口味数据插入到数据库中

菜品分页查询

这个接口的SQL语句涉及到一个多表查询的知识点,我们来分析一下。这里要求我们查的是菜品的所有信息以及它所属的分类(分类名),故SQL语句如下:

select 
d.*,c.name as categoryName # 指定查询dish表所有的数据和category表的name,起别名categoryName有利于数据的封装
from
dish d # 给dish表起别名d
left outer join # 左外连接,将dish表和category表连接起来
category c # 给category表起别名c
on d.category_id=c.id # 描述连接关系

完整的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>

删除菜品

在删除菜品接口,我们既可以一个一个删除,也可以批量删除,所以在该接口,我们controller层只需传进一个id的集合,即可覆盖一个一个删除和批量删除两种情况。但有两种类型的菜品我们不能删除1.起售中的菜品不能删除 2.被套餐关联的菜品不能删除(下面有重点分析)。所以我们需要在实现类中判断以下菜品能不能删:

	@Override
    @Transactional
    public void deleteByIds(List<Long> ids) {
        //判断菜品是否能删除
        //1.起售中的菜品不能删除
        for (Long id : ids) {
            Dish dish=dishMapper.getById(id);
            if(dish.getStatus()== StatusConstant.ENABLE){
                //菜品处于起售,不能删
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        }
        //2.被套餐关联的菜品不能删除
        List<Long> setMealIds=setMealDishMapper.getSetMealIdByDishId(ids);
        //如果根据菜品id查询出了套餐id,则关联了,不能删
        if (setMealIds!=null&&setMealIds.size()>0){
            //又被套餐关联的菜品,不能删
            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
        }

        //两种删除方法
        //1.for循环一个一个删除菜品表中的菜品数据
        for (Long id : ids) {
            dishMapper.deleteById(id);
            //删除菜品关联的口味数据
            dishFlavorMapper.deleteByDishId(id);
        }
        //2.动态SQL批量删除菜品数据
        dishMapper.deleteByIds(ids);
    }

这里重点分析一下被套餐关联的菜品不能删除的实现方法。我们的数据库中有三张表,一张是菜品表,另一个是套餐表,还有一个表示套餐与菜品之间关系的setmeal_dish表,这个表中有两个字段,一个是dish_id,一个是setmael_id,分别与菜品表中的菜品id和套餐表中的套餐id对应,我们只需要获得菜品的id(dish_id),使用这个dish_id去setmeal_dish表中查套餐的id(setmeal_id),查到了说明菜品关联了套餐,不能删。

在SetmealDishMapper中:

//做一些菜品表和套餐表间的多表操作
@Mapper
public interface SetmealDishMapper {
    //根据菜品id查询套餐id,查出来的套餐id可能有多个,故使用集合
    List<Long> getSetMealIdByDishId(List<Long> dishIds);
}

在映射文件中:

	<select id="getSetMealIdByDishId" 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>

修改菜品

在修改菜品接口,我们需要分别修改菜品的信息以及菜品口味的信息,另外在修改口味的操作中,我们再使用update语句来修改的话面临的情况比较多会比较麻烦,这里可以直接先把原来的口味数据删除再插入新的口味数据,这样也变相达到了一个修改口味数据的操作,所以在DishServiceImpl实现类中:

	@Override
    public void updateWithFlavor(DishDTO dishDTO) {
        Dish dish=new Dish();
        BeanUtils.copyProperties(dishDTO,dish);
        //修改菜品表
        dishMapper.update(dish);

        //修改口味表
        //1.先删除原有的口味信息
        dishFlavorMapper.deleteByDishId(dishDTO.getId());
        //2.重新插入口味信息
        List<DishFlavor> flavors=dishDTO.getFlavors();
        if(flavors!=null&&flavors.size()>0){
            for (DishFlavor flavor : flavors) {
                //遍历插入关联菜品的id
                flavor.setDishId(dishDTO.getId());
            }
        }
        dishFlavorMapper.insertBatch(flavors);
    }

那么苍穹外卖第三天的学习就分享完了,感谢您的停留。

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值