存在的问题:
解决问题思路:
代码实现
1.自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法
在com.sky下创建annotaion包,专门用于注解
创建AutoFill,自定义注解,用于表示某个方法需要进行功能字段自动填充处理
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
// 数据库操作类型:UPDATE INSERT
OperationType value();
}
2.自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值
在com.sky下创建aspect包,专门用于切面类
自定义切面,实现公共字段自动填充出来逻辑
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@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 now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
// 根据当前不同的操作类型,为对应的属性通过反射来复制
if (operationType == 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().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){
// 为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) {
e.printStackTrace();
}
}
}
}
// 在EmployeeMapper
@AutoFill(value = OperationType.INSERT)
void insert(Employee employee);
@AutoFill(value = OperationType.UPDATE)
void update(Employee employee);
// CategoryMapper
@AutoFill(value = OperationType.INSERT)
void insert(Category category);
@AutoFill(value = OperationType.UPDATE)
void update(Category category);
在EmployeeServiceImpl中,把相关填充公共字段的代码注释掉
在CategoryServiceImpl中,把相关填充公共字段的代码注释掉
业务规则:
接口设计:
创建CommonController,开发文件上传接口
@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {
@Autowired
private AliOssUtil aliOssUtil;
/**
* 文件上传
* @param file
* @return
*/
@PostMapping("/upload")
@ApiOperation("文件上传")
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.info("文件上传失败:{}", e);
}
return Result.error(MessageConstant.UPLOAD_FAILED);
}
}
创建OssConfiguration配置类,用于创建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());
}
}
开发文件上传接口,配置文件:
# 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}
# application-dev.yml
sky:
alioss:
endpoint: oss-cn-beijing.aliyuncs.com
access-key-id: LTAI5tPeFLzsPPT8gG3LPW64
access-key-secret: U6k1brOZ8gaOIXv3nXbulGTUzy6Pd7
bucket-name: sky-itcast
注意:配置文件中的相关阿里云服务器OSS配置,请使用自己的申请的服务器
创建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();
}
}
在 DishServiceImpl 中实现 save方法,及其父类接口:
@Service
@Slf4j
public class DishServiceImpl implements DishService {
@Autowired
private DishMapper dishMapper;
@Autowired
private DishFlavorMapper dishFlavorMapper;
/**
* 新增菜品和对应的口味数据
* @param dishDTO
*/
@Override
@Transactional
public void saveWithFlavor(DishDTO dishDTO) {
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO, dish);
// 向菜品表插入数据
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);
}
}
}
开发新增菜品接口DishMapper,并用@AutoFill实现公共字段自动填充
/**
* 插入菜品数据
* @param dish
*/
@AutoFill(value = OperationType.INSERT)
void insert(Dish dish);
由于插入语句较长,将SQL语句写入DishMapper.xml文件中
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, status, create_time, update_time, create_user, update_user)
values
(#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})
insert>
mapper>
除了添加菜品到dish表中,还需要添加dish_flavor口味到表中
创建DishFlavorMapper接口,实现插入菜品口味的接口:
@Mapper
public interface DishFlavorMapper {
/**
* 批量插入口味数据
* @param flavors
*/
void insertBatch(List<DishFlavor> flavors);
}
在DishFlavorMapper.xml中写入相关批量插入口味数据的SQL语句
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);
}
在DishServiceImpl中实现pagequery方法,及其父类接口
/**
* 菜品分页查询
* @param dishPageQueryDTO
* @return
*/
@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());
}
在DishMapper中实现pagequery方法,动态SQL语句将写在xml文件中
/**
* 菜品分页查询
* @param dishPageQueryDTO
* @return
*/
Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);
DIshMapper.xml中,写入分页查询的动态语句
<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>
业务规则:
在DishController中创建删除接口
/**
* 菜品批量删除
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids){
log.info("菜品批量删除:{}", ids);
dishService.deleteBatch(ids);
return Result.success();
}
在DishServiceImpl中实现delete方法,及其父类接口
/**
* 菜品批量删除
* @param ids
*/
@Override
@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.isEmpty()) {
// 当前菜品被套餐关联了,不能删除
throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
}
// 删除菜品表中的菜品数据
for (Long id : ids) {
dishMapper.deleteById(id);
// 删除菜品关联的口味数据
dishFlavorMapper.deleteByDishId(id);
}
}
在DIshMapper中,实现根据id查询菜品方法 (查询是否存在起售中的菜品),实现删除菜品方法
/**
* 根据id查询菜品
* @param id
* @return
*/
@Select("select * from dish where id = #{id}")
Dish getById(Long id);
/**
* 根据id删除菜品
* @param id
*/
@Delete("Delete from dish where id = #{id}")
void deleteById(Long id);
创建SetmealDishMapper, 判断当前菜品是否能够删除(是否被套餐关联)
@Mapper
public interface SetmealDishMapper {
/**
* 根据菜品id查询对应的套餐id
* @param ids
* @return
*/
// select setmeal_id from setmeal_dish where dish_id in (1,2,3....)
List<Long> getSetmealIdsByDishIds(List<Long> ids);
}
在SetmealDishMapper.xml文件中,写入动态SQL语句
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="ids" item="dishId" separator="," open="(" close=")">
#{dishId}
foreach>
select>
mapper>
在DIshFlavorMapper中,根据菜品id删除对应的口味数据
/**
* 根据菜品id删除对应的口味数据
* @param dishId
*/
@Delete("Delete from dish_flavor where dish_id = #{dishId}")
void deleteByDishId(Long dishId);
接口设计:
在DishController中,根据id查询菜品和关联的口味数据
/**
* 根据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);
}
在DishServiceImpl中,根据id查询菜品和关联的口味数据,及其父类接口
/**
* 根据id查询菜品和对应的口味数据
* @param id
* @return
*/
@Override
public DishVO getByIdWithFlavor(Long id) {
// 根据id查询菜品数据
Dish dish = dishMapper.getById(id);
// 根据菜品id查询口味数据
List<DishFlavor> flavor = dishFlavorMapper.getByDishId(id);
// 将查询到的数据封装到VO
DishVO dishVO = new DishVO();
BeanUtils.copyProperties(dish, dishVO);
dishVO.setFlavors(flavor);
return dishVO;
}
在DIshFlavorMapper中,实现通过菜品id查口味的接口
/**
* 根据菜品id查询对应的口味数据
* @param dishId
* @return
*/
@Select("Select * from dish_flavor where dish_id = #{dishId} ")
List<DishFlavor> getByDishId(Long dishId);
在DishController中,修改菜品和关联的口味数据
/**
* 修改菜品和关联的口味数据
* @param dishDTO
* @return
*/
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO){
log.info("修改菜品:{}", dishDTO);
dishService.updateWithFlavor(dishDTO);
return Result.success();
}
在DishServiceImpl中,实现修改菜品和关联的口味数据的方法,及其父类接口
/**
* 根据id修改菜品基本信息和对应的口味信息
* @param dishDTO
*/
@Override
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.isEmpty()) {
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishDTO.getId());
});
// 向口味表插入n条数据
dishFlavorMapper.insertBatch(flavors);
}
}
在DIshMapper中,实现修改菜品的接口
/**
* 根据id动态修改菜品
* @param dish
*/
@AutoFill(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>