课程内容
- 公共字段自动填充
- 新增菜品
- 菜品分页查询
- 删除菜品
- 修改菜品
公共字段自动填充
问题分析
业务表中的公共字段
序号 | 字段名 | 含义 | 数据类型 |
---|---|---|---|
1 | create_time | 创建时间 | datetime |
2 | create_user | 创建人id | bigint |
3 | update_time | 修改时间 | datetime |
4 | update_user | 修改人id | bigint |
而目前 在我们的项目中处理字段都在每一个业务方法下逐个进行赋值操作 | |||
代码冗余 不便于后期维护 |
实现思路
使用aop切面编程思想 完成公共字段填充功能
序号 | 字段名 | 含义 | 数据类型 | 操作类型 |
---|---|---|---|---|
1 | create_time | 创建时间 | datetime | insert |
2 | create_user | 创建人id | bigint | insert |
3 | update_time | 修改时间 | datetime | insert、update |
4 | update_user | 修改人id | bigint | insert、update |
- 定义注解AutoFill 用于标识需要进行公共字段自动填充的方法
- 自定义切面类AutoAspect 统一拦截加入AutoFill注解的方法
- 通过反射为公共字段赋值
- 在mapper方法上加入AutoFill注解
代码开发
- 根据如上步骤开发
- 进入到sky-server模块,创建com.sky.annotation包。
//自定义注解 , 用于标识某个方法需要进行功能字段填充处理
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//指定当时的操作类型
OperationType value();
}
- 创建切面类
- 在sky-server模块,创建com.sky.aspect包。
//自定义切面
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
//指定切入点
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.anntotation.AutoFill)")
public void autoFillPointCut() {
}
//使用前置通知
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) {
//获取当前被拦截到的方法上的数据类型
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) {
log.warn("没有传入参数,无法进行自动填充");
return;
}
Object entity = args[0]; //假设第一个参数是实体对象
//准备赋值数据
LocalDateTime now = LocalDateTime.now(); //当前时间
Long currentId = BaseContext.getCurrentId(); //当前登录用户id
//根据同操作类型 使用反射赋值
if (operationType == OperationType.INSERT) {
//插入操作 为四个公共字段赋值
try {
Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser", Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
//通过反射为对象赋值
setCreateTime.invoke(entity, now); //设置创建时间
setUpdateUser.invoke(entity, now); //设置更新时间
setUpdateUser.invoke(entity, currentId); //设置创建用户id
setCreateUser.invoke(entity, currentId); //设置更新用户id
} catch (Exception e) {
throw new RuntimeException(e);
}
}else if (operationType == OperationType.UPDATE) {
//更新操作 为两个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
setUpdateTime.invoke(entity, now); //设置更新时间
setUpdateUser.invoke(entity, currentId); //设置更新用户id
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
- 在需要拦截的mapper上添加注解
- server层中对应的赋值代码可以删除
新增菜品
需求分析
业务规则
- 菜品名称必须唯一
- 菜品必须属于某个分类下 不能单独存在
- 新增彩品时根据情况选择菜品的口味
- 每个菜品对应一张图片
接口设计
- 根据类型查询分类(已完成)
- 文件上传
- 新增菜品
表设计
设计菜品表和菜品口味表
字段名 | 数据类型 | 说明 | 备注 |
---|---|---|---|
id | bigint | 主键 | 自增 |
name | varchar(32) | 菜品名称 | 唯一 |
category_id | bigint | 分类id | 逻辑外键 |
price | decimal(10,2) | 菜品价格 | |
image | varchar(255) | 图片路径 | |
description | varchar(255) | 菜品描述 | |
status | int | 售卖状态 | 1起售 0停售 |
create_time | datetime | 创建时间 | |
update_time | datetime | 最后修改时间 | |
create_user | bigint | 创建人id | |
update_user | bigint | 最后修改人id |
字段名 | 数据类型 | 说明 | 备注 |
---|---|---|---|
id | bigint | 主键 | 自增 |
dish_id | bigint | 菜品id | 逻辑外键 |
name | varchar(32) | 口味名称 | |
value | varchar(255) | 口味值 |
代码开发
文件上传实现
- 采用阿里云的oss服务进行文件存储
- 在sky-server模块 application-dev.yml中配置
sky:
alioss:
endpoint: oss-cn-hangzhou.aliyuncs.com
access-key-id: LTAI5tPeFLzsPPT8gG3LPW64
access-key-secret: U6k1brOZ8gaOIXv3nXbulGTUzy6Pd7//记得换成自己的
bucket-name: sky-take-out
- application.yml中
spring:
profiles:
active: dev #设置环境
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}
- 读取oss配置(源代码已提供)
- 在server模块中生成oss工具类对象
/**
* 配置类,用于创建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());
}
}
- 在server层中定义接口
//通用接口
@Slf4j
@RestController
@RequestMapping("/admin/common")
@ApiOperation("通用接口")
public class CommonController {
@Autowired
private AliOssUtil aliOssUtil;
@PostMapping("/upload")
@ApiOperation("文件上传")
public Result<String> upload(MultipartFile file) {
try {
//原始文件名
String originalFilename = file.getOriginalFilename();
//截取原始文件名的后缀
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
//用uuid防止重名
String object = UUID.randomUUID().toString() + suffix;
String filePath = aliOssUtil.upload(file.getBytes() , object);
return Result.success(filePath);
} catch (IOException e) {
log.error("文件上传失败", e);
throw new RuntimeException(e);
}
}
}
新增菜品实现
- 设计dto类
- 在pojo模块中
@Data
public class DishDTO implements Serializable {
private Long id;
//菜品名称
private String name;
//菜品分类id
private Long categoryId;
//菜品价格
private BigDecimal price;
//图片
private String image;
//描述信息
private String description;
//0 停售 1 起售
private Integer status;
//口味
private List<DishFlavor> flavors = new ArrayList<>();
}
- Controller层
- 创建DishController
/**
* 菜品管理
*/
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
/**
* 新增菜品
*
* @param dishDTO
* @return
*/
@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishDTO) {
log.info("新增菜品:{}", dishDTO);
dishService.saveWithFlavor(dishDTO);//后绪步骤开发
return Result.success();
}
}
- server层接口以及实现
package com.sky.service;
import com.sky.dto.DishDTO;
import com.sky.entity.Dish;
public interface DishService {
/**
* 新增菜品和对应的口味
*
* @param dishDTO
*/
public void saveWithFlavor(DishDTO dishDTO);
}
package com.sky.service.impl;
@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();
List<DishFlavor> flavors = dishDTO.getFlavors();
if (flavors != null && flavors.size() > 0) {
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishId);
});
//向口味表插入n条数据
dishFlavorMapper.insertBatch(flavors);//后绪步骤实现
}
}
}
- Mapper层
- DishMapper中添加
/**
* 插入菜品数据
*
* @param dish
*/
@AutoFill(value = OperationType.INSERT)
void insert(Dish dish);
- 在/resources/mapper中创建DishMapper.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>
- 创建DishFlavorMapper.java
@Mapper
public interface DishFlavorMapper {
/**
* 批量插入口味数据
* @param flavors
*/
void insertBatch(List<DishFlavor> flavors);
}
- 在/resources/mapper中创建DishFlavorMapper.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.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>
菜品分页查询
需求分析
- 根据页码展示菜品信息
- 每页展示10条数据
- 分页查询时可以根据需要输入菜品名称 菜品分类 菜品状态进行查询
代码开发
设计DTO类
- 根据菜品分页查询接口定义设计对应的DTO
- 在sky-pojo模块中
@Data
public class DishPageQueryDTO implements Serializable {
private int page;
private int pageSize;
private String name;
private Integer categoryId; //分类id
private Integer status; //状态 0表示禁用 1表示启用
}
设计VO类
- 在sky-pojo模块中
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DishVO implements Serializable {
private Long id;
//菜品名称
private String name;
//菜品分类id
private Long categoryId;
//菜品价格
private BigDecimal price;
//图片
private String image;
//描述信息
private String description;
//0 停售 1 起售
private Integer status;
//更新时间
private LocalDateTime updateTime;
//分类名称
private String categoryName;
//菜品关联的口味
private List<DishFlavor> flavors = new ArrayList<>();
}
Controller层
- 根据接口定义创建DishController 的page分页查询方法
/**
* 菜品分页查询
*
* @param dishPageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
log.info("菜品分页查询:{}", dishPageQueryDTO);
PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);//后绪步骤定义
return Result.success(pageResult);
}
Service层接口
- 在Service创建分页查询方法
PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO);
- 在Impl层实现
@Override
public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
long total = page.getTotal();
List<DishVO> records = page.getResult();
return new PageResult(total, records);
}
Mapper层
- 在DishMapper接口中声明PageQuery方法
Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);
- 在DishMapper.xml中编写sql
<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>
菜品删除
需求分析
- 可以一次删除一个菜品,也可以批量删除菜品
- 起售中的菜品不能删除
- 被套餐关联的菜品不能删除
- 删除菜品后,关联的口味数据也需要删除掉
注意事项
- 在dish表中删除菜品基本数据时,同时,也要把关联在dish_flavor表中的数据一块删除。
- setmeal_dish表为菜品和套餐关联的中间表。
- 若删除的菜品数据关联着某个套餐,此时,删除失败。
- 若要删除套餐关联的菜品数据,先解除两者关联,再对菜品进行删除。
代码开发
Controller
- 根据删除菜品的接口定义在DishController
/**
* 菜品批量删除
*
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids) {
log.info("菜品批量删除:{}", ids);
dishService.deleteBatch(ids);//后绪步骤实现
return Result.success();
}
Service
- 在DishService接口中声明deleteBatch方法
void deleteBatch(List<Long> ids);
- 继续实现
@Autowired
private SetmealDishMapper setmealDishMapper;
/**
* 菜品批量删除
*
* @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);//后绪步骤实现
}
}
Mapper层
- 在DishMapper层中声明getById方法
/**
* 根据主键查询菜品
*
* @param id
* @return
*/
@Select("select * from dish where id = #{id}")
Dish getById(Long id);
- 创建SetmealDishMapper,声明getSetmealIdsByDishIds方法,并在xml文件中编写SQL:
package com.sky.mapper;
import com.sky.entity.SetmealDish;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SetmealDishMapper {
/**
* 根据菜品id查询对应的套餐id
*
* @param dishIds
* @return
*/
//select setmeal_id from setmeal_dish where dish_id in (1,2,3,4)
List<Long> getSetmealIdsByDishIds(List<Long> dishIds);
}
- 编写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.SetmealDishMapper">
<select id="getSetmealIdsByDishIds" resultType="java.lang.Long">
select setmeal_id from setmeal_dish where dish_id in
<foreach collection="dishIds" item="dishId" separator="," open="(" close=")">
#{dishId}
</foreach>
</select>
</mapper>
- 声明deleteById方法并配置SQL
/**
* 根据主键删除菜品数据
*
* @param id
*/
@Delete("delete from dish where id = #{id}")
void deleteById(Long id);
- 声明deleteByDishId方法并配置SQL
/**
* 根据菜品id删除对应的口味数据
* @param dishId
*/
@Delete("delete from dish_flavor where dish_id = #{dishId}")
void deleteByDishId(Long dishId);
修改菜品
需求分析
根据id查询菜品
- 根据类型查询分类(已实现)
- 文件上传(已实现)
- 修改菜品
代码开发
- 根据id查询菜品实现
Controller
- 在DishController中创建方法
/**
* 根据id查询菜品
*
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询菜品")
public Result<DishVO> getById(@PathVariable Long id) {
log.info("根据id查询菜品:{}", id);
DishVO dishVO = dishService.getByIdWithFlavor(id);//后绪步骤实现
return Result.success(dishVO);
}
Service
- 在DishService中声明方法
/**
* 根据id查询菜品和对应的口味数据
*
* @param id
* @return
*/
DishVO getByIdWithFlavor(Long id);
- 记得实现
/**
* 根据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;
}
Mapper
- 在DishFlavorMapper中声明getByDishId方法,并配置SQL
/**
* 根据菜品id查询对应的口味数据
* @param dishId
* @return
*/
@Select("select * from dish_flavor where dish_id = #{dishId}")
List<DishFlavor> getByDishId(Long dishId);
- 修改菜品实现
Controller
- 根据修改菜品的接口定义在DishController中创建方法:
/**
* 修改菜品
*
* @param dishDTO
* @return
*/
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO) {
log.info("修改菜品:{}", dishDTO);
dishService.updateWithFlavor(dishDTO);
return Result.success();
}
- 在DishService接口中声明updateWithFlavor方法:
/**
* 根据id修改菜品基本信息和对应的口味信息
*
* @param dishDTO
*/
void updateWithFlavor(DishDTO dishDTO);
Service
- 在DishServiceImpl中实现updateWithFlavor方法:
/**
* 根据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);
}
}
Mapper
- 在DishMapper中,声明update方法:
/**
* 根据id动态修改菜品数据
*
* @param dish
*/
@AutoFill(value = OperationType.UPDATE)
void update(Dish dish);
- 并在DishMapper.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>