itheima苍穹外卖项目学习笔记--Day3:菜品管理

苍穹外卖

  • Day3: 菜品管理
    • a.公共字段自动填充 (技术开发)
    • b. 新增菜品
      • (1). 文件上传
      • (2). 菜品新增
    • c. 菜品分页查询
    • d. 删除菜品
    • e. 修改菜品
      • (1). 根据id查询菜品
      • (2). 修改菜品

Day3: 菜品管理

a.公共字段自动填充 (技术开发)

存在的问题:

  • 很多业务数据表中都有许多相同的字段
  • 如:create_time,create_user,update_time,update_user

解决问题思路:

  • 使用相同的操作类型 (自动填充策略)
  • create_time,create_user 使用 Insert
  • update_time,update_user 使用 Insert/update
  • 技术点:枚举、注解、AOP、反射

代码实现

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

  • 在com.sky下创建annotaion包,专门用于注解

  • 创建AutoFill,自定义注解,用于表示某个方法需要进行功能字段自动填充处理

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    // 数据库操作类型:UPDATE INSERT
    OperationType value();
}
  • 2.自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

  • 在com.sky下创建aspect包,专门用于切面类

  • 自定义切面,实现公共字段自动填充出来逻辑

@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){
        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 now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        // 根据当前不同的操作类型,为对应的属性通过反射来复制
        if (operationType == 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().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){
            // 为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) {
                e.printStackTrace();
            }
        }
    }

}
  • 3.在 Mapper 的方法上加入 AutoFill 注解
// 在EmployeeMapper
@AutoFill(value = OperationType.INSERT)
void insert(Employee employee);

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

// CategoryMapper
@AutoFill(value = OperationType.INSERT)
void insert(Category category);

@AutoFill(value = OperationType.UPDATE)
void update(Category category);

在EmployeeServiceImpl中,把相关填充公共字段的代码注释掉

在CategoryServiceImpl中,把相关填充公共字段的代码注释掉

b. 新增菜品

业务规则:

  • 菜品名称必须是唯一的
  • 菜品必须属于某个分类下,不能单独存在
  • 新增菜品时可以根据情况选择菜品的口味
  • 每个菜品必须对应一张图片

接口设计:

  • 根据类型查询分类(已完成)
  • 文件上传
  • 新增菜品

(1). 文件上传

创建CommonController,开发文件上传接口

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

    @Autowired
    private AliOssUtil aliOssUtil;

    /**
     * 文件上传
     * @param file
     * @return
     */
    @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.info("文件上传失败:{}", e);
        }

        return Result.error(MessageConstant.UPLOAD_FAILED);
    }
}

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

开发文件上传接口,配置文件:

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

# application-dev.yml
sky:
	alioss:
    	endpoint: oss-cn-beijing.aliyuncs.com
    	access-key-id: LTAI5tPeFLzsPPT8gG3LPW64
    	access-key-secret: U6k1brOZ8gaOIXv3nXbulGTUzy6Pd7
    	bucket-name: sky-itcast

注意:配置文件中的相关阿里云服务器OSS配置,请使用自己的申请的服务器

(2). 菜品新增

创建DishController,开发新增菜品接口

@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {

    @Autowired
    private DishService dishService;

    /**
     * 新增菜品
     * @param dishDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO){
        log.info("新增菜品:{}", dishDTO);
        dishService.saveWithFlavor(dishDTO);
        return Result.success();
    }
}

在 DishServiceImpl 中实现 save方法,及其父类接口:

@Service
@Slf4j
public class DishServiceImpl implements DishService {

    @Autowired
    private DishMapper dishMapper;

    @Autowired
    private DishFlavorMapper dishFlavorMapper;

    /**
     * 新增菜品和对应的口味数据
     * @param dishDTO
     */
    @Override
    @Transactional
    public void saveWithFlavor(DishDTO dishDTO) {

        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);

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

        // 获取insert语句生成主键值
        Long dishId = dish.getId();

        // 获取口味数据
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && flavors.size() > 0) {
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });
            // 向口味表插入n条数据
            dishFlavorMapper.insertBatch(flavors);
        }

    }
}

开发新增菜品接口DishMapper,并用@AutoFill实现公共字段自动填充

/**
* 插入菜品数据
* @param dish
*/
@AutoFill(value = OperationType.INSERT)
void insert(Dish dish);

由于插入语句较长,将SQL语句写入DishMapper.xml文件中


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, status, create_time, update_time, create_user, update_user)
            values
        (#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})
    insert>
mapper>

除了添加菜品到dish表中,还需要添加dish_flavor口味到表中

创建DishFlavorMapper接口,实现插入菜品口味的接口:

@Mapper
public interface DishFlavorMapper {

    /**
     * 批量插入口味数据
     * @param flavors
     */
    void insertBatch(List<DishFlavor> flavors);
}

在DishFlavorMapper.xml中写入相关批量插入口味数据的SQL语句


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<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>

c. 菜品分页查询

业务规则:

  • 根据页码展示菜品信息
  • 每页展示10条数据
  • 分页查询时可以根据需要输入菜品名称、菜品分类、菜品状态进行查询

在DishController中创建分页接口

/**
* 菜品分页查询
* @param dishPageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO){
    log.info("菜品分页查询,参数为:{}",dishPageQueryDTO);
    PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
    return Result.success(pageResult);
}

在DishServiceImpl中实现pagequery方法,及其父类接口

/**
* 菜品分页查询
* @param dishPageQueryDTO
* @return
*/
@Override
public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
    PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
    Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
    return new PageResult(page.getTotal(), page.getResult());
}

在DishMapper中实现pagequery方法,动态SQL语句将写在xml文件中

/**
* 菜品分页查询
* @param dishPageQueryDTO
* @return
*/
Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);

DIshMapper.xml中,写入分页查询的动态语句

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

d. 删除菜品

业务规则:

  • 可以一次删除一个菜品,也可以批量删除菜品
  • 起售中的菜品不能删除
  • 被套餐关联的菜品不能删除
  • 删除菜品后,关联的口味数据也需要删除掉

在DishController中创建删除接口

/**
* 菜品批量删除
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids){
    log.info("菜品批量删除:{}", ids);
    dishService.deleteBatch(ids);
    return Result.success();
}

在DishServiceImpl中实现delete方法,及其父类接口

/**
* 菜品批量删除
* @param ids
*/
@Override
@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.isEmpty()) {
        // 当前菜品被套餐关联了,不能删除
        throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
    }

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

在DIshMapper中,实现根据id查询菜品方法 (查询是否存在起售中的菜品),实现删除菜品方法

/**
* 根据id查询菜品
* @param id
* @return
*/
@Select("select * from dish where id = #{id}")
Dish getById(Long id);

/**
* 根据id删除菜品
* @param id
*/
@Delete("Delete from dish where id = #{id}")
void deleteById(Long id);

创建SetmealDishMapper, 判断当前菜品是否能够删除(是否被套餐关联)

@Mapper
public interface SetmealDishMapper {

    /**
     * 根据菜品id查询对应的套餐id
     * @param ids
     * @return
     */
    // select setmeal_id from setmeal_dish where dish_id in (1,2,3....)
    List<Long> getSetmealIdsByDishIds(List<Long> ids);
}

在SetmealDishMapper.xml文件中,写入动态SQL语句


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="ids" item="dishId" separator="," open="(" close=")">
            #{dishId}
        foreach>
    select>
    
mapper>

在DIshFlavorMapper中,根据菜品id删除对应的口味数据

/**
* 根据菜品id删除对应的口味数据
* @param dishId
*/
@Delete("Delete from dish_flavor where dish_id = #{dishId}")
void deleteByDishId(Long dishId);

e. 修改菜品

接口设计:

  • 根据id查询菜品
  • 根据类型查询分类(已实现)
  • 文件上传(已实现)
  • 修改菜品

(1). 根据id查询菜品

在DishController中,根据id查询菜品和关联的口味数据

/**
* 根据id查询菜品和对应的口味数据
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询菜品")
public Result<DishVO> getById(@PathVariable Long id){
    log.info("根据id查询菜品:{}", id);
    DishVO dishVO = dishService.getByIdWithFlavor(id);
    return Result.success(dishVO);
}

在DishServiceImpl中,根据id查询菜品和关联的口味数据,及其父类接口

/**
* 根据id查询菜品和对应的口味数据
* @param id
* @return
*/
@Override
public DishVO getByIdWithFlavor(Long id) {
    // 根据id查询菜品数据
    Dish dish = dishMapper.getById(id);

    // 根据菜品id查询口味数据
    List<DishFlavor> flavor = dishFlavorMapper.getByDishId(id);

    // 将查询到的数据封装到VO
    DishVO dishVO = new DishVO();
    BeanUtils.copyProperties(dish, dishVO);
    dishVO.setFlavors(flavor);

    return dishVO;
}

在DIshFlavorMapper中,实现通过菜品id查口味的接口

/**
* 根据菜品id查询对应的口味数据
* @param dishId
* @return
*/
@Select("Select * from dish_flavor where dish_id = #{dishId} ")
List<DishFlavor> getByDishId(Long dishId);

(2). 修改菜品

在DishController中,修改菜品和关联的口味数据

/**
* 修改菜品和关联的口味数据
* @param dishDTO
* @return
*/
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO){
    log.info("修改菜品:{}", dishDTO);
    dishService.updateWithFlavor(dishDTO);
    return Result.success();
}

在DishServiceImpl中,实现修改菜品和关联的口味数据的方法,及其父类接口

/**
* 根据id修改菜品基本信息和对应的口味信息
* @param dishDTO
*/
@Override
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.isEmpty()) {
        flavors.forEach(dishFlavor -> {
            dishFlavor.setDishId(dishDTO.getId());
        });
        // 向口味表插入n条数据
        dishFlavorMapper.insertBatch(flavors);
    }
}

在DIshMapper中,实现修改菜品的接口

/**
* 根据id动态修改菜品
* @param dish
*/
@AutoFill(OperationType.UPDATE)
void update(Dish dish);

在DIshMapper.xml中,写入动态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>

你可能感兴趣的:(苍穹外卖开发笔记,学习,笔记,java,spring,mybatis)