菜品管理
公共字段自动填充
对员工,菜品,套餐、、的设置时间等,导致代码冗余。
使用切面来为这些方法统一设置
枚举:标识当前操作的类型(不同来类型操作的字段名不同)。反射(为公共字段赋值)
为什么要用反射赋值,不能直接赋值嘛?因为获取的实体类对象可能不一样,比如员工和菜品,要使用set方法赋值你就要强转为某个实体类对象,这样写这个就没意义了,所以获取方法对象通过反射来赋值。
1.创建自定义注解
annotation.AutoFill.java
@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) {//插入链接点的参数值
//获取到当前被栏截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//从连接点获得方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType value = 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();//用户ID
//根据当前不同的操作类型,为对应的属性通过反射来赋值
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 //开启注解方式的事务管理
开始新增菜品
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>