苍穹外卖后端开发(Day3)
菜品管理
公共字段自动填充
对于创建时间、创建人id,修改时间,修改人id等字段 重复性代码进行统一编写
自定义注解AutoFill
,用于标识需要进行公共字段自动填充的方法
package com.sky.annotation;
/**
* 自定义注解,用于标识某个方法需要进行功能字段自动填充处理。
* 这个注解可以应用于方法上,用于指示在特定的数据库操作时
* 需要自动填充某些功能字段。
*/
@Target(ElementType.METHOD) // 指定注解可以用于方法
@Retention(RetentionPolicy.RUNTIME) // 指定注解在运行时可用
public @interface AutoFill {
/**
* 数据库操作类型:用于指示自动填充的操作类型。
* 可选值包括 UPDATE 和 INSERT。
*
* @return 操作类型
*/
OperationType value();
}
自定义切面类AutoFillAspect
, 统一拦截加入了AutoFill
注解的方法,通过反射为公共字段赋值。
package com.sky.aspect;
/**
* 自定义切面, 实现公共字段自动填充
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 定义切入点,匹配com.sky.mapper包下的所有方法,并且方法上需有@AutoFill注解
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut() {
}
/**
* 前置通知,在目标方法执行前进行公共字段赋值
*
* @param joinPoint 连接点,表示被拦截的方法调用
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) {
log.info("开始进行公共字段自动填充...");
// 获取当前被拦截方法的签名(方法信息)
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 从方法签名中获取@AutoFill注解
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
// 获取操作类型(INSERT或UPDATE)
OperationType operationType = autoFill.value();
// 获取目标方法的参数(假设第一个参数是实体对象)
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
return; // 如果没有参数则直接返回
}
// 获取实体对象(目标方法的第一个参数)
Object entity = args[0];
// 获取当前时间和当前操作用户ID
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
// 根据操作类型判断是INSERT还是UPDATE操作
if (operationType == OperationType.INSERT) {
try {
// 通过反射调用实体对象的set方法,赋值创建和更新时间、创建和更新用户
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) {
try {
// 通过反射调用实体对象的set方法,赋值更新时间和更新用户
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(); // 捕获异常并打印
}
}
}
}
在Mapper
的方法上加入AutoFill
注解
@AutoFill(value = OperationType.INSERT)
@AutoFill(value = OperationType.UPDATE)
新增菜品
产品原型
业务规则:
- 菜品名称必须是唯一的
- 菜品必须属于某个分类下,不能单独存在
- 新增菜品时可以根据情况选择菜品的口味
- 每个菜品必须对应一张图片
接口设计:
- 根据类型查询分配
- 文件上传
- 新增菜品
数据库设计(dish菜品表和dish_flavor口味表):
配置OSS图像存储服务
可以通过下面的Properties文件配置OSS的属性。
package com.sky.properties;
@Component
@ConfigurationProperties(prefix = "sky.alioss")
@Data
public class AliOssProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}
在application-dev
中详细配置id, key等。
通用接口
package com.sky.controller.admin;
/**
* 通用接口
*/
@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {
@Autowired
private AliOssUtil aliOssUtil;
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
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.error("文件上传失败:{}", e);
}
return Result.error(MessageConstant.UPLOAD_FAILED);
}
}
菜品接口
package com.sky.controller.admin;
@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();
}
}
在DishServiceImpl
中实现saveWithFlavor
package com.sky.service.impl;
@Service
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);
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);
}
}
}
在Mapper中实现SQL
DishMapper
<?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)
values
(#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})
</insert>
</mapper>
-
useGeneratedKeys="true"
: 表示在插入操作后,使用数据库生成的主键值。这个选项通常与主键自增(如 MySQL 的 AUTO_INCREMENT)一起使用。 -
keyProperty="id"
: 指定存储生成主键值的对象属性。在插入成功后,数据库生成的主键值会被设置到这个属性上。
DishFlavorMapper
<?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="df" separator=",">
(#{df.dishId}, #{df.name}, #{df.value})
</foreach>
</insert>
</mapper>
菜品分页查询
需求分析和设计
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);
}
实现pageQuery
@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());
}
Mapper
<select id="pageQuery" resultType="com.sky.vo.DishVO">
select d.*, c.name as categoryName from dish d left 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>
菜品删除
业务规则:
- 可以一次删除一个菜品,也可以批量删除多个菜品
- 启售的菜品不能删除
- 被套餐关联的菜品不能删除
- 删除菜品后,关联的口味数据也需要删除掉
@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids){
log.info("菜品批量删除:{}",ids);
dishService.deleteBatch(ids);
return Result.success();
}