文章目录
前言
菜品管理,这部分包括公共字段自动填充、新增菜品。
公共字段自动填充在这部分主要使用到的技术有AOP切面编程,这里面又涉及到了反射机制,还有怎么去自定义注解之类的。
新增菜品这块新学的知识就是文件上传,这里用了阿里云OSS,把文件上传到云端存储,然后就是涉及到两个表之间的关联,怎么从dish_flavor表去获得另dish表中的dishId。
一、公共字段填充(枚举、自定义注解、AOP、反射)
1、问题分析
在为业务表中的公共字段设值时出现代码冗余,不利于后期维护。代码如下:
//设置当前记录的创建时间、修改时间、创建人、修改人
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());
//设置创建时间、修改时间、创建人、修改人
category.setCreateTime(LocalDateTime.now());
category.setUpdateTime(LocalDateTime.now());
category.setCreateUser(BaseContext.getCurrentId());
category.setUpdateUser(BaseContext.getCurrentId());
2、实现思路
1、自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法
@Target(ElementType.METHOD) //说明Annotation所修饰的对象范围
@Retention(RetentionPolicy.RUNTIME) //定义该Annotation生命周期(编译/运行)
public @interface AutoFill {
//枚举类OperationType,记录数据库操作类型:UPDATE INSERT
OperationType value();
}
2、自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值
@Aspect //定义这个类为切面类
@Component //把切面类放到容器中
@Slf4j
public class AutoFillAspect {
// 具体切入点 告诉Spring当前通知方法要套用到哪个目标方法上
@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 time = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
//根据当前不同的操作类型,为对应的属性通过反射来赋值
if(operationType == OperationType.INSERT){
//为四个公共字段赋值
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setCreateTime.invoke(entity,time);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,time);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
log.error("公共字段自动填充失败:{}", e.getMessage());
}
}else if(operationType == OperationType.UPDATE){
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,time);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
log.error("公共字段自动填充失败:{}", e.getMessage());
}
}
}
}
3、在Mapper 的方法上加入 AutoFill 注解
@Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
" VALUES" +
" (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
@AutoFill(value = OperationType.INSERT)
void insert(Category category);
@AutoFill(value = OperationType.UPDATE)
void update(Category category);
二、新增菜品
1、产品原型
业务规则
菜品名称必须是唯一的
菜品必须属于某个分类下,不能单独存在
新增菜品时可以根据情况选择菜品的口味
每个菜品必须对应张图片
2、接口设计
1、根据类型查询分类(第二天的分类管理部分已完成 public Result<List> queryByType(Integer type))
2、文件上传
3、新增菜品
3、数据库设计
这里涉及的表有两个:dish菜品表和dish_flavor口味表
4、代码开发
4.1 文件上传(阿里云OSS)
不理解的可以看这个视频,讲解很详细。
文件上传
1、开发文件上传接口
相关配置文件
#application-dev.yml
#这里可以自己在阿里云对象存储OSS中去创建一个Bucket,就能将图片上传到云端。
alioss:
endpoint: oss-cn-beijing.aliyuncs.com
access-key-id: LTAI5tPeFLzsPPT8gG3LPW64
access-key-secret: U6k1brOZ8gaOIXv3nXbulGTUzy6Pd7
bucket-name: sky-itcast
#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}
配置类 用于创建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());
}
}
通用方法
@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {
@Autowired
private AliOssUtil aliOssUtil;
@PostMapping("/upload")
@ApiOperation("文件上传")
public Result<String> upload(MultipartFile file){
log.info("文件上传:{}",file);
try {
//原始文件名
String originalFilename = file.getOriginalFilename();
//截取原始文件名的后缀 1.jpg
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);
}
}
4.2 新增菜品
1、根据接口请求数据设计了实体类DishDTO
2、controller层
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishDTO){
log.info("新增菜品{}",dishDTO);
//除了关联菜品表,同时也要把口味信息保存
dishService.saveWithFlavor(dishDTO);
return Result.success();
}
}
3、在DishService声明aveWithFlavor方法(新增菜品和对应的口味),在DishServiceImpl实现方法
@Transactional //涉及多个表的操作,要保证数据一致性,所以使用事务注解,保证方法的原子性
@Override
public void saveWithFlavor(DishDTO dishDTO) {
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO,dish); //因为dishDTO还包含口味数据,而我们只需要插入菜品
//1、向菜品表插入1条数据,一次只能插入一条
dishMapper.insert(dish);
//2、向口味表插入n条数据
//获取insert语句生成的主键值
Long dishId = dish.getId(); //口味的dishId属性要从菜品dish表中获取
//拿到封装在dishDTO中口味的信息
List<DishFlavor> flavors = dishDTO.getFlavors();
//判断集合中有没有数据
if(flavors != null && flavors.size() > 0){
//遍历flavors集合,每次遍历就设置dishId的值
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishId);
});
//批量插入
dishFlavorMapper.insertBatch(flavors);
}
}
4、在DishMapper接口中写insert方法
@AutoFill(OperationType.INSERT) //update_time create_time update_user create_user自动填充
void insert(Dish dish);
在DishMapper.xml文件中写sql语句
<!--口味的dishId属性要从菜品dish表中获取,
所以要将数据库表中的id值赋给dish实体类中的Id值,然后再赋给口味表dish_flavor中的dishId
useGeneratedKeys:true 获取主键值
keyProperty:id 将主键值赋给id属性-->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into sky_take_out.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>
5、在DishFlavorMapper接口写insertBatch方法,批量插入口味数据
void insertBatch(List<DishFlavor> flavors);
在DishFlavorMapper.xml文件中写sql语句
<insert id="insertBatch">
insert into sky_take_out.dish_flavor(dish_id, name, value) values
<!--遍历lavors集合中的元素(dishFlavor对象),没遍历一次就用,分隔-->
<foreach collection="flavors" item="df" separator=",">
<!--dishId要从dish表中获取-->
(#{df.dishId},#{df.name},#{df.value})
</foreach>
</insert>
。