#苍穹外卖#(day3-4)
目录
day03菜品管理
1 公共字段自动填充
2 自定义切面类
2 新增菜品
3 菜品分页查询
4 删除菜品
5 修改菜品
day04 套餐管理
day03菜品管理
1 公共字段自动填充
1 自定义注解AutoFill
2 自定义切面类
对公共字段进行处理,下面这些是公共字段
常量方法,代码规范
将不需要对公共的属性进行赋值,这些方法底层都是调用update与insert的方法,我们从切面当中已经处理了这些公共字段那在调用这些方法时
就不需要再在业务代码当中手动重复设置时间(将下面这些类的对这些公共字段设置时间的语句进行删除)
加上注解(加上的就会自动填充公共字段)
切面类
package com.sky.aspect;import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.time.LocalDateTime;/*** 自定义切面,用于自动填充公共字段处理逻辑*/@Aspect
@Component
@Slf4j
public class AutoFillAspect {/*** 切入点表达式 com.sky.mapper 包下的所有类中的所有方法并且有 @AutoFill 注解的方法*/@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")public void autoFillPointCut() {}/*** 前置通知,在目标方法执行前执行*/@Before("autoFillPointCut()")public void autoFill(JoinPoint joinPoint) {log.info("开始进行公共字段自动填充...");//获取到当前杯拦截的方法上的数据库操作类型AutoFill autoFill = joinPoint.getTarget().getClass().getAnnotation(AutoFill.class);//获得注解对象OperationType operationType = autoFill.value();//数据库操作类型//获取当前杯拦截的方法的参数--实体对象Object[] args = joinPoint.getArgs();if (args == null || args.length == 0) {return;}Object entity = args[0];log.info("当前自动填充的实体对象:{}", entity.toString());//准备赋值的数据LocalDateTime createTime = LocalDateTime.now();Long currentId = BaseContext.getCurrentId();//根据当前不同的操作类型,为对应的实体对象通过反射来赋值if (operationType == OperationType.INSERT) {//四个公共字段:createTime、createUser、updateTime、updateUser赋值try {//获取当前实体类中的对应方法(使用本地的常量方法名)Method createTimeMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method createUserMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);Method updateTimeMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method updateUserMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//通过反射为实体对象赋值createTimeMethod.invoke(entity, createTime);createUserMethod.invoke(entity, currentId);updateTimeMethod.invoke(entity, createTime);updateUserMethod.invoke(entity, currentId);log.info("为实体类 {} 赋值成功", entity.getClass());} catch (Exception e) {e.printStackTrace();}} else if (operationType == OperationType.UPDATE) {try {//两个公共字段:updateTime、updateUser赋值Method updateTimeMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method updateUserMethod = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);updateTimeMethod.invoke(entity, createTime);updateUserMethod.invoke(entity, currentId);log.info("为实体类 {} 赋值成功", entity.getClass());} catch (Exception e) {e.printStackTrace();}}}
}
2 新增菜品
使用到三个接口:
根据类型查询分类(前面已完成)/文件上传/ 新增菜品
需要结合阿里云服务器进行相关操作
最终实现对图片的上传
AliOssProperties 是配置属性映射类,负责从配置文件当中读取OSS的配置项
OssConfiguration定义SpringBean的创建逻辑,将AliOssProPerties的配置值传递给AliOssUtil
AliOssUtil工具类,封装阿里云OSS的上传功能,是实际执行文件操作的类
AliOssProperties
package com.sky.properties;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Component
@ConfigurationProperties(prefix = "sky.alioss")
@Data
public class AliOssProperties {private String endpoint;private String accessKeyId;private String accessKeySecret;private String bucketName;}
AliOssUtil
package com.sky.utils;import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;@Data
@AllArgsConstructor
@Slf4j
public class AliOssUtil {private String endpoint;private String accessKeyId;private String accessKeySecret;private String bucketName;/*** 文件上传** @param bytes* @param objectName* @return*/public String upload(byte[] bytes, String objectName) {// 创建OSSClient实例。OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);try {// 创建PutObject请求。ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));} catch (OSSException oe) {System.out.println("Caught an OSSException, which means your request made it to OSS, "+ "but was rejected with an error response for some reason.");System.out.println("Error Message:" + oe.getErrorMessage());System.out.println("Error Code:" + oe.getErrorCode());System.out.println("Request ID:" + oe.getRequestId());System.out.println("Host ID:" + oe.getHostId());} catch (ClientException ce) {System.out.println("Caught an ClientException, which means the client encountered "+ "a serious internal problem while trying to communicate with OSS, "+ "such as not being able to access the network.");System.out.println("Error Message:" + ce.getMessage());} finally {if (ossClient != null) {ossClient.shutdown();}}//文件访问路径规则 https://BucketName.Endpoint/ObjectNameStringBuilder stringBuilder = new StringBuilder("https://");stringBuilder.append(bucketName).append(".").append(endpoint).append("/").append(objectName);log.info("文件上传到:{}", stringBuilder.toString());return stringBuilder.toString();}
}
OssConfiguration
package com.sky.config;import com.sky.properties.AliOssProperties;
import com.sky.utils.AliOssUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 配置类:用于 注入阿里云OSS配置信息**/@Slf4j
@Configuration
public class OssConfiguration {//在方法当中创建对象,需要将AiliOssUtil当中的属性赋值,属性的值是从配置文件中获取,最后再封装成对象AilOssProperties返回//如果容器当中没有AilOssProperties这个对象,则创建这个对象,否则不创建@ConditionalOnMissingBean@Bean@ConditionalOnMissingBeanpublic AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){log.info("开始创建阿里云OSS对象:{}",aliOssProperties);return new AliOssUtil(aliOssProperties.getEndpoint(),aliOssProperties.getAccessKeyId(),aliOssProperties.getAccessKeySecret(),aliOssProperties.getBucketName());}
}
controller层
1 一个文件上传的通用接口
package com.sky.controller.admin;import com.sky.constant.MessageConstant;
import com.sky.result.Result;
import com.sky.utils.AliOssUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.UUID;/*** 通用接口* @author mxsky*/@Slf4j
@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
public class CommonController {@Autowiredprivate AliOssUtil aliOssUtil;/*** 文件上传* @param file* @return*/@RequestMapping("/upload")@ApiOperation("文件上传")public Result<String> upload(MultipartFile file) {log.info("文件上传{}",file);try {//获取文件原始名称String originalFilename = file.getOriginalFilename();// 获取文件后缀assert originalFilename != null;String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));// 生成新文件名String objectName = UUID.randomUUID().toString() + suffix;// 文件的请求路径String filPath = aliOssUtil.upload(file.getBytes(), objectName);return Result.success(filPath);} catch (IOException e) {log.error("文件上传失败:{}", e);}return Result.error(MessageConstant.UPLOAD_FAILED);}}
对菜品的新增
package com.sky.controller.admin;import com.sky.dto.DishDTO;
import com.sky.result.Result;
import com.sky.service.DishService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@Slf4j
@RestController
@RequestMapping("/admin/dish")
@Api(tags="菜品相关接口")
public class DishController {@Autowiredprivate DishService dishService;/*** 新增菜品* @param dishDTO* @return*/@ApiOperation("新增菜品")@PostMappingpublic Result save(@RequestBody DishDTO dishDTO){log.info("新增菜品:{}", dishDTO);dishService.saveWithFlavor(dishDTO);return Result.success();}
}
service层
接口
package com.sky.service;import com.sky.dto.DishDTO;public interface DishService {// 新增菜品和对应的口味数据public void saveWithFlavor(DishDTO dishDTO) ;}
实现类
package com.sky.service.impl;import com.sky.dto.DishDTO;
import com.sky.entity.Dish;
import com.sky.entity.DishFlavor;
import com.sky.mapper.DishFlavorMapper;
import com.sky.mapper.DishMapper;
import com.sky.service.DishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;@Slf4j
@Service
public class DishServiceImpl implements DishService {@Autowiredprivate DishMapper dishMapper;@Autowiredprivate DishFlavorMapper dishFlavorMapper;/*** 新增菜品和对应的口味数据** @param dishDTO*/@Transactionalpublic void saveWithFlavor(DishDTO dishDTO) {Dish dish = new Dish();BeanUtils.copyProperties(dishDTO, dish);//向菜品表中插入1条数据dishMapper.insert(dish);//获取菜品idLong dishId = dish.getId();//向口味表插入n条数据List<DishFlavor> flavors = dishDTO.getFlavors();if (flavors != null && !flavors.isEmpty()) {flavors.forEach(dishFlavor -> {dishFlavor.setDishId(dishId);});// 向口味表插入n条数据dishFlavorMapper.insertBatch(flavors);}}
}
mapper接口
插入单个
package com.sky.mapper;import com.sky.annotation.AutoFill;
import com.sky.entity.Dish;
import com.sky.enumeration.OperationType;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;@Mapper
public interface DishMapper {/*** 根据分类id查询菜品数量* @param categoryId* @return*/@Select("select count(id) from dish where category_id = #{categoryId}")Integer countByCategoryId(Long categoryId);/***插入数据* @return*/// 公共字段自动填充@AutoFill(OperationType.INSERT)void insert(Dish dish);
}
批量插入口味的数据
package com.sky.mapper;import com.sky.entity.DishFlavor;
import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper
public interface DishFlavorMapper {/*** 批量插入口味数据* @param flavors*/void insertBatch(List<DishFlavor> flavors);
}
上面两个Mapper接口的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"><!-- useGenerateKeys获取主键值 --><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>
<?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.DishFlavorMapper"><insert id="insertBatch">insert into dish_flavor (dish_id, name, value) values<foreach collection="flavors" item="flavor" separator=",">(#{flavor.dishId},#{flavor.name},#{flavor.value})</foreach></insert>
</mapper>
错误bug修改
1 AutoFillAspect 当中这里出错
2 在jwtTokenAdminInterceptor当中没有及时对校验过后的id给BaseContext赋值
第一个问题导致Aspect切面类无法生效,第二个导致id的设置无法获取到,从而无法赋值
3 菜品分页查询
首先,在DishController类当中编写相关方法,
类中定义了pageQuery方法,我们需要实现,这样我们就来到了Service层
顺便我们再将他的实现类给完善
完善DishMapper接口当中的方法
最后在Mapper的xml当中编写适当的动态SQL语句
4 删除菜品
首先在controller业务层完善相关代码
再在Service层编写删除的功能代码
编写Service的实现类(删除功能)
package com.sky.service.impl;import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.entity.DishFlavor;
import com.sky.exception.DeletionNotAllowedException;
import com.sky.mapper.DishFlavorMapper;
import com.sky.mapper.DishMapper;
import com.sky.mapper.SetMealDishMapper;
import com.sky.result.PageResult;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;@Slf4j
@Service
public class DishServiceImpl implements DishService {@Autowiredprivate DishMapper dishMapper;@Autowiredprivate DishFlavorMapper dishFlavorMapper;@Autowiredprivate SetMealDishMapper setMealDishMapper;/*** 新增菜品和对应的口味数据** @param dishDTO*/@Override@Transactionalpublic void saveWithFlavor(DishDTO dishDTO) {Dish dish = new Dish();BeanUtils.copyProperties(dishDTO, dish);//向菜品表中插入1条数据dishMapper.insert(dish);//获取菜品idLong dishId = dish.getId();//向口味表插入n条数据List<DishFlavor> flavors = dishDTO.getFlavors();if (flavors != null && !flavors.isEmpty()) {flavors.forEach(dishFlavor -> {dishFlavor.setDishId(dishId);});// 向口味表插入n条数据dishFlavorMapper.insertBatch(flavors);}}/*** 菜品分页查询** @param dishPageQueryDTO* @return*/@Overridepublic PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);return new PageResult(page.getTotal(), page.getResult());}/*** 批量删除菜品** @param ids*/@Transactional@Overridepublic void deleteBatch(List<Long> ids) {//判断当前菜品是否在售for (Long id : ids) {Dish dish = dishMapper.getById(id);//根据id查询菜品数据(添加方法)if (dish.getStatus() == 1) {//如果菜品处于启售状态,则不能删除throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);}}//判断当前菜品是否被套餐关联List<Long> dishIds = setMealDishMapper.getSetMealIdsByDishIds(ids);if (dishIds != null && !dishIds.isEmpty()) {//如果存在关联的套餐,则不能删除throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);}//删除菜品数据for (Long id : ids) {dishMapper.deleteById(id);}//删除菜品对应的口味数据for (Long id : ids) {dishFlavorMapper.deleteByDishId(id);}}
}
删除功能还需补全对应的Mapper接口实现对数据库当中数据的操作
1 根据id查询菜品
2 根据id删除菜品
3 这里需要创建一个额外接口实现对口味的业务数据操作
对应的xml
4 根据菜品id删除对应的口味
这里还可以进行改进
5 修改菜品
根据id查询菜品
首先在controller层当中编写相关方法
Service中实现
接口的实现类
在Mapper中补全对应的SQL查询语句
修改菜品
controller层业务代码实现
service层的修改相关业务代码
实现类的编写
Mapper接口的实现
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"><!-- useGenerateKeys获取主键值 --><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><!-- 根据菜品id批量删除 --><delete id="deleteByIds">delete from dish where id in<foreach collection="ids" open="(" close=")" separator="," item="id">#{id}</foreach></delete><!-- 菜品分页查询 --><select id="pageQuery" resultType="com.sky.vo.DishVO">select d.*,c.name as categoryNamefrom dish dleft join category c on d.category_id = c.id<where><if test="name != null and name != ''">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><!-- 根据id动态修改菜品数据 --><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></mapper>
day04 套餐管理
接口设计(共涉及到4个接口):
-
根据类型查询分类(已完成)
-
根据分类id查询菜品
-
图片上传(已完成)
-
新增套餐
新增套餐时没有将主键回显导致主键无法获取到