【苍穹外卖】项目日记(超详细) day3

D3

公共字段自动填充

业务表中的公共字段

代码冗余,不便于后期维护。

实现思路如下,

自定义注解AutoFill

/**
 * 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型 update insert
    OperationType value();
}

只有在update和insert时,填充公共字段才有意义

自定义切面类AutoFillAspect,用来拦截切入点

切入点就是要进行增强的方法,而每个方法就称为一个连接点,切入点可以匹配多个方法,也可以匹配一个方法。

在此,连接点可以理解为是update和select方法

/**
     * 前置通知,在通知中进行公共字段的赋值
     * @param joinPoint
     */
    @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();
            }
        }

使用反射实现公共字段填充赋值

新增菜品

需求分析

这里不仅希望上传文件,而且还需要实现在当前页面展示图片的功能,也就是回显,所以data是必要的,用于记录存储在阿里云照片的绝对路径。

由于当前需求路径为common,需要创建CommonController,实现文件上传。基本格式如下:

阿里云OSS对象存储的使用

这里如果之前看过黑马的JavaWeb的话应该还记得怎么用,没看过的小伙伴,我在这里简单的演示下获取配置文件时需要填写的各项数据。

打开阿里云OSS官网,搜索Bucket,点击创建Bucket

  • 命名没啥好说的,自己编个独特的名字就可以,不可以与他人重复。
  • 地域看自己喜欢设置,影响的是这个Endpoint。
获取|创建密钥,打开RAM 访问控制台点击AccessKey

后续按照他的提示操作即可,创建或者查看自己之前的AccessKey都可以这么操作。

重要提示:创建完成后务必保存accessKeyIdaccessKeySecret,现在已经不再允许查询密码,为确保下次使用请务必注意!

新建配置类,用于创建AliossUtil对象

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

创建并初始化AliossUtil对象

表现层:重构文件名以及上传文件

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

    @Autowired
    private AliOssUtil aliOssUtil;
    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping("/upload")
    @ApiOperation("文件上传")
    public Result<String> upload(MultipartFile file){
        log.info("文件上传: {}", file);

        //原始文件名
        String originalFilename = file.getOriginalFilename();
        //截取文件名后缀
        String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
        //构造新文件名称
        String objectName = UUID.randomUUID().toString() + extension;
        //文件的请求路径
        try {
            String filePath = aliOssUtil.upload(file.getBytes(), objectName);
            return Result.success(filePath);
        } catch (IOException e) {
            log.info("上传失败:{}", e);
        }
        return null;
    }
}

重点注意:这部分我犯了一个错误,没有将Bucket设置为公共读,导致在进行前后端联调时,上传文件后图片不会回显。

排查过程感兴趣的可以私信我(有一些小技巧),在这里不浪费篇幅了

向口味表插入数据

@Service
@Slf4j
public class DishServiceImpl implements DishService {

    @Autowired
    private DishMapper dishMapper;

    @Autowired
    private DishFlavorMapper dishFlavorMapper;

    /**
     * 新增菜品及口味
     * @param dishDTO
     */
    @Transactional
    public void saveWithFlavor(DishDTO dishDTO) {

        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);
        //向菜品表插入1条数据
        dishMapper.insert(dish);

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

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

在获取insert语句生成的主键值时,xml文件中需要设置

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

使用生成的主键,属性为id

菜品分页查询

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

与员工分页查询大差不差,借助PageHelper实现。

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

在这里建议看一下DTO和VO的内容,是用来封装接收参数以及返回参数,基本上CRUD都是这么写的,像板子一样。

值得注意的是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>

由于涉及到了两个name字段,d.name和c.name,这里需要起别名加以区分,否则MyBatis在映射时会优先将name字段映射为哪个值会变得不确定


删除菜品

可以把单个删除看作是特殊的批量删除。

这里完全可以使用字符串接收参数,然后根据逗号分隔获取每个id值

/**
 * 菜品批量删除
 * @param ids
 * @return
 */
@DeleteMapping
@ApiOperation(value = "菜品批量删除")
public Result delete(String ids){
    return Result.success();
}

但是我们可以交给SpringMVC处理,他会自动解析,加上注解@RequestParam。

表示获取参数ids并封装为list对象

/**
 * 菜品批量删除
 * @param ids
 * @return
 */
@DeleteMapping
@ApiOperation(value = "菜品批量删除")
public Result delete(@RequestParam List<Long> ids){
    return Result.success();
}

服务层

这里需要考虑当前菜品是否能删除(是否存在起售中的菜品、是否被套餐关联),如果可以删除那么考虑批量删除。

/**
 * 菜品批量删除
 * @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);
}*/

    //根据菜品id集合批量删除菜品数据
    //sql: delete from dish where id in (??)
    dishMapper.deleteByIds(ids);
    //根据菜品id集合批零删除关联的口味数据
    //delete from dish_flavor where dish_id in (??)
    dishFlavorMapper.deleteByDishIds(ids);
}

为了提高删除效率,这里把遍历删除改为传入id集合,批量删除

知识点:动态SQL的编写

//根据菜品id集合批量删除菜品数据
//sql: delete from dish where id in (??)
<delete id="deleteByIds">
  delete from dish where dish_id in
  <foreach collection="ids" open="(" close=")" separator="," item="id">
    #{id}
  </foreach>
</delete>

//根据菜品id集合批零删除关联的口味数据
//delete from dish_flavor where dish_id in (??)
<delete id="deleteByDishIds">
        delete from dish_flavor where dish_id in
        <foreach collection="dishIds" open="(" close=")" item="dishId" separator=",">
            #{id}
        </foreach>
</delete>

修改菜品

需求

根据id查询菜品

修改菜品

表现层

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

}

难点在于服务层,因为根据接口文档可以得知返回的参数为两部分,包括菜品数据部分和菜品口味部分,所以调用mapper方法后需要合并封装为一个对象来返回。

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

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

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

return dishVO;
}

修改菜品的服务层,这部分比较简单

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

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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值