项目日志 苍穹外卖 企业级开发项目《苍穹外卖》(二) 何平安 2024-02-04 2024-08-03 添加公共通用注解 因为接下来的许多接口需要用到create_time,update_time数据库添加语句等,这种会增加代码的重复性,这时这时直接添加一个注释然后添加到Mapper类里面。就是利用AOP进行公共段自动填充技术
先在server模块下创建annotation.AutoFill注解类,里面编写:
1 2 3 4 5 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AutoFill { OperationType value () ; }
OperationType是一个枚举类,里面有UPDATE,INSERT
然后在在server里面创建aspect.AutoFillAspect类,里面编写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Aspect @Component @Slf4j public class AutoFillAspect { @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") public void autoFillPointCut () {} @Before("autoFillPointCut()") public void autoFill () { log.info("开始进行共享字段自动填充..." ); } }
execution(* com.sky.mapper.. (..)) && @annotation(com.sky.annotation.AutoFill)的意思就是切入点为com.sky.mapper下的所有类的所有方法并且有AutoFill注解的。Before就是一个输出日志。
菜品管理 文件上传 先在Controller层里面添加一个通用接口类:CommonController,里面编写文件上传接口,将文件上传到阿里云OSS。
配置阿里OSS直接在yml里面编写nameid和key就行,会发现编写时有提示,因为在common模块里面已经添加了AliOSSProtites的配置属性注解:@ConfigurationProperties(prefix = “sky.alioss”)
yml:注意’:’后面有个空格
1 2 3 4 5 6 7 8 9 10 11 12 13 sky: jwt: admin-secret-key: itcast admin-ttl: 7200000 admin-token-name: token alioss: access-key-id: ${sky.alioss.access-key-id} access-key-secret: ${sky.alioss.access-key-secret} bucket-name: ${sky.alioss.bucket-name} endpoint: ${sky.alioss.endpoint}
这里不直接写是为了规范,隐藏key等密码。真实数据在dev-yml里面编写,因为上边写了
1 2 3 spring: profiles: active: dev
在server模块的config里面新建一个OssConfiguration的类,里面编写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @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()); } }
添加菜品 接下来好多都是SpringMvc和MyBatis框架的内容了…
直接添加菜品和对应口味。主要是impl里面的,其他的都比较简单:口味因为可以有许多味道,所以就用List来添加,.forEach来循环添加数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Transactional public void saveWithFlaver (DishDTO dishDTO) { Dish dish=new Dish (); BeanUtils.copyProperties(dishDTO,dish); dishMapper.insert(dish); Long dishId = dish.getId(); List<DishFlavor> flavors =dishDTO.getFlavors(); if (flavors !=null && flavors.size()>0 ){ flavors.forEach(dishFlavor -> { dishFlavor.setDishId(dishId); }); dishFlavorMapper.insertBatch(flavors); } }
然后是SQL添加语句:
1 2 3 4 5 6 <insert id ="insertBatch" > insert into dish_flavor (dish_id, name, value) VALUES <foreach collection ="flavors" item ="df" > (#{df.dishId}),#{df.name},#{df.value}) </foreach > </insert >
删除菜品 首先要确定需要满足的要求,满足后才删,最后再删除关联口味数据。Controller老样子。
DishServiceImpl类里面编写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public void deleteQuery (List<Long> ids) { for (Long id : ids) { Dish dish=dishMapper.getById(id); if (dish.getStatus()== StatusConstant.ENABLE){ throw new DeletionNotAllowedException (MessageConstant.DISH_ON_SALE); } } for (Long id : ids) { 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,SetmealDishMapper和它的xml,用于判断菜品是否被某个套餐关联。
Mapper里面:
1 2 3 4 5 6 7 8 9 @Mapper public interface SetmealDishMapper { List<Long> getSetmealIdsByDishIds (List<Long> dishIds) ; }
xml动态SQL查询语句:
1 2 3 4 5 6 <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 >
套餐管理 1. 新增套餐 1.1 需求分析和设计 产品原型:
业务规则:
套餐名称唯一
套餐必须属于某个分类
套餐必须包含菜品
名称、分类、价格、图片为必填项
添加菜品窗口需要根据分类类型来展示菜品
新增的套餐默认为停售状态
接口设计(共涉及到4个接口):
根据类型查询分类(已完成)
根据分类id查询菜品
图片上传(已完成)
新增套餐
数据库设计:
setmeal表为套餐表,用于存储套餐的信息。具体表结构如下:
字段名
数据类型
说明
备注
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
setmeal_dish表为套餐菜品关系表,用于存储套餐和菜品的关联关系。具体表结构如下:
字段名
数据类型
说明
备注
id
bigint
主键
自增
setmeal_id
bigint
套餐id
逻辑外键
dish_id
bigint
菜品id
逻辑外键
name
varchar(32)
菜品名称
冗余字段
price
decimal(10,2)
菜品单价
冗余字段
copies
int
菜品份数
1.2 代码实现 1.2.1 DishController 1 2 3 4 5 6 7 8 9 10 11 /** * 根据分类id查询菜品 * @param categoryId * @return */ @GetMapping("/list") @ApiOperation("根据分类id查询菜品") public Result<List<Dish>> list(Lo ng categoryId){ List<Dish> list = dishService.list(categoryId); return Result.success(list); }
1.2.2 DishService 1 2 3 4 5 6 /** * 根据分类id查询菜品 * @param categoryId * @return */ List<Dish> list(Long categoryId);
1.2.3 DishServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 /** * 根据分类id查询菜品 * @param categoryId * @return */ public List<Dish> list(Long categoryId) { Dish dish = Dish.builder() .categoryId(categoryId) .status(StatusConstant.ENABLE) .build(); return dishMapper.list(dish); }
1.2.4 DishMapper 1 2 3 4 5 6 /** * 动态条件查询菜品 * @param dish * @return */ List<Dish> list(Dish dish);
1.2.5 DishMapper.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <select id="list" resultType="Dish" parameterType="Dish"> select * from dish <where> <if test="name != null"> and name like concat('%',#{name},'%') </if> <if test="categoryId != null"> and category_id = #{categoryId} </if> <if test="status != null"> and status = #{status} </if> </where> order by create_time desc </select>
1.2.6 SetmealController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /** * 套餐管理 */ @RestController @RequestMapping("/admin/setmeal") @Api(tags = "套餐相关接口") @Slf4j public class SetmealController { @Autowired private SetmealService setmealService; /** * 新增套餐 * @param setmealDTO * @return */ @PostMapping @ApiOperation("新增套餐") public Result save(@RequestBody SetmealDTO setmealDTO) { setmealService.saveWithDish(setmealDTO); return Result.success(); } }
1.2.7 SetmealService 1 2 3 4 5 6 7 8 public interface SetmealService { /** * 新增套餐,同时需要保存套餐和菜品的关联关系 * @param setmealDTO */ void saveWithDish(SetmealDTO setmealDTO); }
1.2.8 SetmealServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @Service @Slf4j public class SetmealServiceImpl implements SetmealService { @Autowired private SetmealMapper setmealMapper; @Autowired private SetmealDishMapper setmealDishMapper; @Autowired private DishMapper dishMapper; @Transactional public void saveWithDish (SetmealDTO setmealDTO) { Setmeal setmeal = new Setmeal (); BeanUtils.copyProperties(setmealDTO, setmeal); setmealMapper.insert(setmeal); Long setmealId = setmeal.getId(); List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes(); setmealDishes.forEach(setmealDish -> { setmealDish.setSetmealId(setmealId); }); setmealDishMapper.insertBatch(setmealDishes); } }
1.2.9 SetmealMapper 1 2 3 4 5 6 /** * 新增套餐 * @param setmeal */ @AutoFill(OperationType.INSERT) void insert(Setmeal setmeal);
1.2.10 SetmealMapper.xml 1 2 3 4 5 6 <insert id ="insert" parameterType ="Setmeal" useGeneratedKeys ="true" keyProperty ="id" > insert into setmeal (category_id, name, price, status, description, image, create_time, update_time, create_user, update_user) values (#{categoryId}, #{name}, #{price}, #{status}, #{description}, #{image}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}) </insert >
1.2.11 SetmealDishMapper 1 2 3 4 5 /** * 批量保存套餐和菜品的关联关系 * @param setmealDishes */ void insertBatch(List<SetmealDish> setmealDishes);
1.2.12 SetmealDishMapper.xml 1 2 3 4 5 6 7 8 <insert id="insertBatch" parameterType="list"> insert into setmeal_dish (setmeal_id,dish_id,name,price,copies) values <foreach collection="setmealDishes" item="sd" separator=","> (#{sd.setmealId},#{sd.dishId},#{sd.name},#{sd.price},#{sd.copies}) </foreach> </insert>
1.3 功能测试 略
2. 套餐分页查询 2.1 需求分析和设计 业务规则:
根据页码进行分页展示
每页展示10条数据
可以根据需要,按照套餐名称、分类、售卖状态进行查询
2.2 代码实现 2.2.1 SetmealController 1 2 3 4 5 6 7 8 9 10 11 /** * 分页查询 * @param setmealPageQueryDTO * @return */ @GetMapping("/page") @ApiOperation("分页查询") public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO) { PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO); return Result.success(pageResult); }
2.2.2 SetmealService 1 2 3 4 5 6 /** * 分页查询 * @param setmealPageQueryDTO * @return */ PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
2.2.3 SetmealServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 public PageResult pageQuery (SetmealPageQueryDTO setmealPageQueryDTO) { int pageNum = setmealPageQueryDTO.getPage(); int pageSize = setmealPageQueryDTO.getPageSize(); PageHelper.startPage(pageNum, pageSize); Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO); return new PageResult (page.getTotal(), page.getResult()); }
2.2.4 SetmealMapper 1 2 3 4 5 6 /** * 分页查询 * @param setmealPageQueryDTO * @return */ Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
2.2.5 SetmealMapper.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <select id ="pageQuery" resultType ="com.sky.vo.SetmealVO" > select s.*,c.name categoryName from setmeal s left join category c on s.category_id = c.id <where > <if test ="name != null" > and s.name like concat('%',#{name},'%') </if > <if test ="status != null" > and s.status = #{status} </if > <if test ="categoryId != null" > and s.category_id = #{categoryId} </if > </where > order by s.create_time desc </select >
2.3 功能测试 略
3. 删除套餐 3.1 需求分析和设计 业务规则:
可以一次删除一个套餐,也可以批量删除套餐
起售中的套餐不能删除
3.2 代码实现 3.2.1 SetmealController 1 2 3 4 5 6 7 8 9 10 11 /** * 批量删除套餐 * @param ids * @return */ @DeleteMapping @ApiOperation("批量删除套餐") public Result delete(@RequestParam List<Long> ids){ setmealService.deleteBatch(ids); return Result.success(); }
3.2.2 SetmealService 1 2 3 4 5 /** * 批量删除套餐 * @param ids */ void deleteBatch(List<Long> ids);
3.2.3 SetmealServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Transactional public void deleteBatch (List<Long> ids) { ids.forEach(id -> { Setmeal setmeal = setmealMapper.getById(id); if (StatusConstant.ENABLE == setmeal.getStatus()){ throw new DeletionNotAllowedException (MessageConstant.SETMEAL_ON_SALE); } }); ids.forEach(setmealId -> { setmealMapper.deleteById(setmealId); setmealDishMapper.deleteBySetmealId(setmealId); }); }
3.2.4 SetmealMapper 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Select("select * from setmeal where id = #{id}") Setmeal getById (Long id) ; @Delete("delete from setmeal where id = #{id}") void deleteById (Long setmealId) ;
3.2.5 SetmealDishMapper 1 2 3 4 5 6 /** * 根据套餐id删除套餐和菜品的关联关系 * @param setmealId */ @Delete("delete from setmeal_dish where setmeal_id = #{setmealId}") void deleteBySetmealId(Long setmealId);
3.3 功能测试 略
4. 修改套餐 4.1 需求分析和设计 接口设计(共涉及到5个接口):
根据id查询套餐
根据类型查询分类(已完成)
根据分类id查询菜品(已完成)
图片上传(已完成)
修改套餐
4.2 代码实现 4.2.1 SetmealController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @GetMapping("/{id}") @ApiOperation("根据id查询套餐") public Result<SetmealVO> getById (@PathVariable Long id) { SetmealVO setmealVO = setmealService.getByIdWithDish(id); return Result.success(setmealVO); } @PutMapping @ApiOperation("修改套餐") public Result update (@RequestBody SetmealDTO setmealDTO) { setmealService.update(setmealDTO); return Result.success(); }
4.2.2 SetmealService 1 2 3 4 5 6 7 8 9 10 11 12 /** * 根据id查询套餐和关联的菜品数据 * @param id * @return */ SetmealVO getByIdWithDish(Long id); /** * 修改套餐 * @param setmealDTO */ void update(SetmealDTO setmealDTO);
4.2.3 SetmealServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public SetmealVO getByIdWithDish (Long id) { Setmeal setmeal = setmealMapper.getById(id); List<SetmealDish> setmealDishes = setmealDishMapper.getBySetmealId(id); SetmealVO setmealVO = new SetmealVO (); BeanUtils.copyProperties(setmeal, setmealVO); setmealVO.setSetmealDishes(setmealDishes); return setmealVO; } @Transactional public void update (SetmealDTO setmealDTO) { Setmeal setmeal = new Setmeal (); BeanUtils.copyProperties(setmealDTO, setmeal); setmealMapper.update(setmeal); Long setmealId = setmealDTO.getId(); setmealDishMapper.deleteBySetmealId(setmealId); List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes(); setmealDishes.forEach(setmealDish -> { setmealDish.setSetmealId(setmealId); }); setmealDishMapper.insertBatch(setmealDishes); }
4.2.4 SetmealDishMapper 1 2 3 4 5 6 7 /** * 根据套餐id查询套餐和菜品的关联关系 * @param setmealId * @return */ @Select("select * from setmeal_dish where setmeal_id = #{setmealId}") List<SetmealDish> getBySetmealId(Long setmealId);
4.3 功能测试 略
5. 起售停售套餐 5.1 需求分析和设计 业务规则:
可以对状态为起售的套餐进行停售操作,可以对状态为停售的套餐进行起售操作
起售的套餐可以展示在用户端,停售的套餐不能展示在用户端
起售套餐时,如果套餐内包含停售的菜品,则不能起
5.2 代码实现 5.2.1 SetmealController 1 2 3 4 5 6 7 8 9 10 11 12 @PostMapping("/status/{status}") @ApiOperation("套餐起售停售") public Result startOrStop (@PathVariable Integer status, Long id) { setmealService.startOrStop(status, id); return Result.success(); }
5.2.2 SetmealService 1 2 3 4 5 6 /** * 套餐起售、停售 * @param status * @param id */ void startOrStop(Integer status, Long id);
5.2.3 SetmealServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public void startOrStop (Integer status, Long id) { if (status == StatusConstant.ENABLE){ List<Dish> dishList = dishMapper.getBySetmealId(id); if (dishList != null && dishList.size() > 0 ){ dishList.forEach(dish -> { if (StatusConstant.DISABLE == dish.getStatus()){ throw new SetmealEnableFailedException (MessageConstant.SETMEAL_ENABLE_FAILED); } }); } } Setmeal setmeal = Setmeal.builder() .id(id) .status(status) .build(); setmealMapper.update(setmeal); }
5.2.4 DishMapper 1 2 3 4 5 6 7 @Select("select a.* from dish a left join setmeal_dish b on a.id = b.dish_id where b.setmeal_id = #{setmealId}") List<Dish> getBySetmealId (Long setmealId) ;
5.3 功能测试 略