黑马苍穹外卖3 菜品管理 AOP+反射+阿里云OSS+基本增删改查

菜品管理

公共字段自动填充

在多个业务表中都有公共字段,如create_time、create_user(insert时用到);update_time,update_user(insert和update时用到)这些。
插入数据的时候需要为这些字段赋值,会有大量重复的冗余set方法代码,后期如果表结构发生变化,代码需要跟着修改,此时就不方便修改(如果后期进行修改要重复一个个进行修改)。
实现思路:自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法。然后自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值。在Mapper的方法上加入AutoFill注解。
在这里插入图片描述
在这里插入图片描述
枚举:标识当前操作的类型(不同来类型操作的字段名不同)。反射(为公共字段赋值)
为什么要用反射赋值,不能直接赋值嘛?因为获取的实体类对象可能不一样,比如员工和菜品,要使用set方法赋值你就要强转为某个实体类对象,这样写这个就没意义了,所以获取方法对象通过反射来赋值。
切入点:对哪些类的哪些方法进行拦截。@Pointcut里面写的是对哪些方法进行拦截,要满足2点:①必须是mapper下的所有类的方法,②还要有AutoFill这个注解。
mapper中使用的示例:

 @AutoFill(OperationType.UPDATE)
    void update(Employee employee);

通知:前置通知,后置通知,环绕通知,异常通知。
1.创建自定义注解
annotation.AutoFill.java 自动填充注解类

Target注解指定加上什么上面,Retention注解指定什么时候用,

@Target(ElementType.METHOD)//指定注解只能加载方法上
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //通过枚举-指定当前属性OperationType
    //数据库操作类型OperationType:就两种 Update 和 Insert
    OperationType value();
}

2.切面类
aspect.AutoFillAspect.java 自动填充的切片类

@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    //这个包里的类和方法,同时还的是加上注解的
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut() {
    }
    
    //前置通知,在sql执行前加上即(公共字段赋值)
    @Before("autoFillPointCut()")//当匹配上切点表达式的执行这个
    public void autoFill(JoinPoint joinPoint) {//插入链接点的参数值

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

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

        //3.准备赋值的数据
        LocalDateTime now = LocalDateTime.now();//时间
        Long currentId = BaseContext.getCurrentId();//用户ID

        //4.根据当前不同的操作类型,为对应的属性通过反射来赋值
        if (value == 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().getDec laredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                //通过反射赋值
                setCreateTime.invoke(entity, now);
                setUpdateTime.invoke(entity, now);
                setCreateUser.invoke(entity, currentId);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }


        } else if (value == 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) {
                throw new RuntimeException(e);
            }
        }
    }
}

菜品-增删改查

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
文件上传接口,请求参数,固定的请求头。
在这里插入图片描述

阿里云

通过互联网对外提供的各种服务。
项目开发中可以直接调用的软甲服务(收费)

阿里云OSS

对象存储服务。通过网路哦存储和调用文本。图片。音频和视频等各种文件。
服务器本地不用存文件,OSS帮我们存储和管理。
在这里插入图片描述
使用步骤:
在这里插入图片描述
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.yml里写具体的值

  alioss:
    endpoint: yourEndpoint
    access-key-id: yourAccessKeyId
    access-key-secret: yourAccessKeySecret 
    bucket-name: yourBucketName

文件上传需要工具类sky-common.src.main.java.com.sky.utils.AliOssUtil.java
类里有这四个属性,通过调用upload后能够返回图片网址
配置类用于创建AliOssUtil对象,给四个属性赋值,值在配置文件读过来。
config.OssConfiguration.java

@Configuration
public class OssConfiguration {

    /**
     * 提供aliyunoss工具类
     *
     * @param aliOssProperties
     * @return
     */
    @Bean //项目启动时就会调用方法创建对象
    @ConditionalOnMissingBean//保证Spring容器里只有一个Util对象,条件对象当没Bean时再创建
    public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties) {
        return new AliOssUtil(aliOssProperties.getEndpoint(),
            aliOssProperties.getAccessKeyId(),
            aliOssProperties.getAccessKeySecret(),
            aliOssProperties.getBucketName());
    }

}

解释一下为什么不用依赖注入:因为该对象已经由于配置文件赋值了,当此对象作为方法传入的时候不是null,而且要搞明白ioc容器是为了得到创建对象的权利。
文件上传CommonController.java

@PostMapping("/upload")
    @ApiOperation("文件上传")
    public Result<String> upload(MultipartFile file) {//要返回阿里云上传网址
        log.info("文件上传:{}",file);
        try {
            //获取文件原始名
            String originalFilename = file.getOriginalFilename();
            //获取文件后缀名
            String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
            //获取文件最终名字
            String fileName = UUID.randomUUID().toString() + extension;

            //获取访问路径

            String filePath = aliOssUtil.upload(file.getBytes(), fileName);
            return Result.success(filePath);
        } catch (IOException e) {
            log.error("文件上传失败:{}", e);
        }

        return Result.error(MessageConstant.UPLOAD_FAILED);
    }

在启动类已经@EnableTransactionManagement //开启注解方式的事务管理
开始新增菜品
一个菜品有多个口味数据,向菜品表插入1条数据,向口味表插入n条数据。
DishServiceImpl.java

@Transactional//涉及几多个数据表,需要保证数据一致性,需要事务注解--保证原子性,全成功或全失败
    @Override
    public void saveWithFlavor(DishDTO dishDTO) {

        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);
        //向菜品表里添加1条数据
        dishMapper.insert(dish);
        //为口味赋值菜品在数据库中的id,利用了主键返回
        Long id = dish.getId();

        List<DishFlavor> flavors = dishDTO.getFlavors();
        //可能用户并没有提交口味数据
        if (flavors != null && flavors.size() > 0) {
            //向口味表里添加多条数据
            flavors.forEach(dishFlavor -> dishFlavor.setDishId(id));
            dishFlavorMapper.insertBatch(flavors);//批量插入
        }

    }

动态sql插入

返回生成的主键值

<insert id="insert" useGeneratedKeys="true" keyProperty="id"><!-- 产生的主键值会赋给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>

查—菜品分页查询

在这里插入图片描述
在这里插入图片描述
返回数据:设计两个表:DIsh表和分类表
Query:不是josn ,而是地址栏+?key=value来查

在这里插入图片描述
所以为什么要创建这个VO呢?:DTO是前传给后 vo是后传给前

@GetMapping("/page")
    @ApiOperation("菜品分页查询")
    public Result<PageResult> page(DishPageQueryDTO dto) {//传入不是json,不用注解
        PageResult pageResult = dishService.pageQuery(dto);
        return Result.success(pageResult);
    }
public PageResult pageQuery(DishPageQueryDTO dto) {
        PageHelper.startPage(dto.getPage(), dto.getPageSize());
        Page<DishVO> dishVOs = dishMapper.pageQuery(dto);//为了适应接口使用DishVO
        return new PageResult(dishVOs.getTotal(), dishVOs.getResult());
    }
Page<DishVO> pageQuery(DishPageQueryDTO dto);

依旧是动态sql
sql语句:左外链接:将左边的表的所有项与右边的表作连接
将菜和种类两个表按照种类id链接起来,以此获得种类名称

select d.*,c.name as categoryName from dish d left outer join category c on d.category_id=c.id

由于种类名称查出来也叫name,所以给字段起别名

<select id="pageQuery" resultType="com.sky.vo.DishVO">
        select d.*,c.name categoryName from dish d left join category c on d.category_id = c.id
        <where><!--毕竟动态sql。给它用where动态拼上DTO的3个属性 -->
            <if test="categoryId! =null">d.category_id=#{categoryId}</if>
            <if test="status!=null">d.status=#{status}</if>
            <if test="name!=null">d.name like concat('%',#{name},'%')</if>
        </where>
        order by d.create_time desc<!--根据创建时间降序 -->
    </select>

删—删除菜品

可以单个删个批量删
在售卖中不能删除
被套餐关联的不能删
删除菜品后,关联的口味也要删
在这里插入图片描述
菜、菜口味、套餐
在这里插入图片描述
Controller:

@ApiOperation("批量删除菜品")
    @DeleteMapping
    public Result delete(@RequestParam List<Long> ids) {//@RequestParam让Spring去解析字符串,然后将分割的字符封装到集合对象中
        dishService.deleteBatch(ids);//批量删除

        //更新缓存
        cleanCache("dish_*");

        return Result.success();
    }

Service:
1.判断能否删除—是否在售卖
2.判断能否删除—是否有套餐关联
3.删除菜品
4.删除关联口味

	@Transactional //事务注解
    @Override
    public void  deleteBatch(List<Long> ids) {
        //判断能否删除---是否在售卖
            //遍历数组id--查询菜品状态
//        for (Long id : ids) {
//            Dish dish = dishMapper.getById(id);
//            if (dish.getStatus() == StatusConstant.ENABLE){//处于起售中,不能删除
//                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
//            }
//        }
        //优化
        List<Dish> dishes = dishMapper.queryUnsale(ids);
        if (dishes != null && dishes.size() > 0) {
            throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
        }

        //判断能否删除---是否有套餐关联
            //查套餐表是否有当前菜品
        List<Long> dishIds = setMealDishMapper.getSetmealIdsByDishIds(ids);
        if (dishIds != null && dishIds.size() > 0) {
            //有关联
            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
        }

        //删除菜品
        dishMapper.deleteBatch(ids);

        //删除关联口味
        dishFlavorMapper.deleteBatchByDishIds(ids);
    }

(1)sql
按照菜id查全部信息,以此得到是否在售卖

@Select("select * from dish where id=#{id}")
    Dish getById(Long id);

(2)sql
// select setmeal id from setmeal dish where dish_id in (1,2,3,4)
List queryUnsale(List ids);传入List列表,用动态sql
SetMealDishMapper.xml

<select id="getSetmealIdsByDishIds" resultType="java.lang.Long">
        select setmeal_id from setmeal_dish where dish_id in
        <foreach collection="dishIds" separator="," item="id" open="(" close=")">
            #{id}
        </foreach>
    </select>

(3)删除菜—传入为列表

<delete id="deleteBatch">
        delete from dish where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>

(4)删除口味—传入为列表

<delete id="deleteBatchByDishIds">
        delete from dish_flavor where dish_id in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </delete>

改—修改菜品

1.查询ID菜品回显
2.查询分类(√)
3.图片上传(√)
4.更新数据
在这里插入图片描述
在这里插入图片描述

(1)ID查菜品信息,数据回显
用DishVO不用DishDTO的原因:DishDTO没有分类名称,而且DTO是接受数据,VO是返回数据。而且,按规定dto 和vo所要执行的功能不同,一个是接前端数据,一个是返回给前端数据的,规范一点。
VO是和前端的交互,DTO是内部传参,POJO是和数据库交互

@ApiOperation("根据id查询菜品")
    @GetMapping("/{id}")
    public Result<DishVO> getById(@PathVariable Long id) {//返回VO,因为还要返回口味信息
        DishVO dishVO = dishService.getByIdWithFlavor(id);
        return Result.success(dishVO);
    }
 @Override
    public DishVO getByIdWithFlavor(Long id) {
        //根据id查询菜品数据
        Dish dish = dishMapper.getById(id);
        //根据菜品id查询口味数据
        List<DishFlavor> flavors = dishFlavorMapper.getByDishId(id);
        //数据封装到VO
        DishVO dishVO = new DishVO();
        BeanUtils.copyProperties(dish, dishVO);
        dishVO.setFlavors(flavors);
        return dishVO;
    

(2)

@ApiOperation("修改菜品")
    @PutMapping
    public Result update(@RequestBody DishDTO dishDTO) {
        dishService.updateWithFlavor(dishDTO);

        //更新缓存
//        Set keys = redisTemplate.keys("dish_*");
//        redisTemplate.delete(keys);
        cleanCache("dish_*");

        return Result.success();
    }

修改菜品以及其口味数据
//先修改菜品信息, //再删除口味信息,//再添加口味信息

 public void updateWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);
        //先修改菜品信息
        dishMapper.update(dish);
        Long id = dishDTO.getId();
        //再删除口味信息
        dishFlavorMapper.deleteBatchByDishIds(Collections.singletonList(id));
        //再添加口味信息
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && flavors.size() > 0) {
            flavors.forEach(dishFlavor -> dishFlavor.setDishId(id));
            //向口味表插入数据
            dishFlavorMapper.insertBatch(flavors);
        }

    }

当相关属性有值时再去修改,所以用的是动态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>
  • 19
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
课堂签到管理系统可以使用Spring框架的MVC、IOC和AOP模块,以及Spring提供的JdbcTemplate进行数据访问。 1. MVC模式:MVC模式分为Model、View和Controller三个部分,分别代表应用程序的数据、视图和控制器。在课堂签到管理系统中,Model层可以使用JdbcTemplate访问数据库,View层可以使用JSP或Thymeleaf模板引擎渲染页面,Controller层则负责处理用户请求并调用Model和View层执行相应的操作。 2. IOC模式:IOC(Inversion of Control)即控制反转,是Spring框架的核心之一。通过IOC容器完成对象的创建和依赖注入,降低了组件之间的耦合性,使得系统更加灵活和可维护。在课堂签到管理系统中,可以通过IOC容器管理Controller和Service层的对象,实现对象的自动装配和依赖注入。 3. AOP模式:AOP(Aspect Oriented Programming)即面向切面编程,是Spring框架的另一个重要特性。通过AOP可以将通用的业务逻辑切面化,比如日志记录、权限校验、事务管理等。在课堂签到管理系统中,可以使用AOP实现日志记录和事务管理等功能。 4. JdbcTemplate:JdbcTemplate是Spring框架提供的一个JDBC模板类,可以简化JDBC代码的编写,并提供了一些便捷的方法和异常处理机制。在课堂签到管理系统中,可以使用JdbcTemplate访问数据库,并实现课程、学生、签到记录等数据的增删操作。 综上所述,课堂签到管理系统可以使用Spring框架的MVC、IOC和AOP模块,以及Spring提供的JdbcTemplate进行开发。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值