企业级开发项目"苍穹外卖"(二)
添加公共通用注解
因为接下来的许多接口需要用到create_time,update_time数据库添加语句等,这种会增加代码的重复性,这时这时直接添加一个注释然后添加到Mapper类里面。就是利用AOP进行公共段自动填充技术
先在server模块下创建annotation.AutoFill注解类,里面编写:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
OperationType value();
}
OperationType是一个枚举类,里面有UPDATE,INSERT
然后在在server里面创建aspect.AutoFillAspect类,里面编写:
@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("开始进行共享字段自动填充...");
}
}
菜品管理
文件上传
先在Controller层里面添加一个通用接口类:CommonController,里面编写文件上传接口,将文件上传到阿里云OSS。
配置阿里OSS直接在yml里面编写nameid和key就行,会发现编写时有提示,因为在common模块里面已经添加了AliOSSProtites的配置属性注解:@ConfigurationProperties(prefix = "sky.alioss")
yml:注意':'后面有个空格
sky:
jwt:
# 设置jwt签名加密时使用的秘钥
admin-secret-key: itcast
# 设置jwt过期时间
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里面编写,因为上边写了
spring:
profiles:
active: dev
在server模块的config里面新建一个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());
}
}
添加菜品
接下来好多都是SpringMvc和MyBatis框架的内容了...
直接添加菜品和对应口味。主要是impl里面的,其他的都比较简单:口味因为可以有许多味道,所以就用List来添加,.forEach来循环添加数据
/**
* 新增菜品和对应口味
* @param dishDTO
*/
@Transactional
public void saveWithFlaver(DishDTO dishDTO) {
Dish dish=new Dish();
BeanUtils.copyProperties(dishDTO,dish);
dishMapper.insert(dish);
//获取insert语句生成的id值
Long dishId =dish.getId();
//向口味添加n条数据
List<DishFlavor> flavors =dishDTO.getFlavors();
//确定口味数据提交进来了
if (flavors !=null && flavors.size()>0){
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishId);
});
dishFlavorMapper.insertBatch(flavors);
}
}
然后是SQL添加语句:
<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类里面编写:
/**
* 批量删除菜品
* @param ids
*/
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里面:
@Mapper
public interface SetmealDishMapper {
/**
* 根据菜品id查询套餐id
* @param dishIds
* @return
*/
List<Long> getSetmealIdsByDishIds(List<Long> dishIds);
}
xml动态SQL查询语句:
<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 需求分析和设计
产品原型:
业务规则:
-
套餐名称唯一
-
套餐必须属于某个分类
-
套餐必须包含菜品
-
名称、分类、价格、图片为必填项
-
添加菜品窗口需要根据分类类型来展示菜品
-
新增的套餐默认为停售状态
接口设计(共涉及到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
/**
* 根据分类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
/**
* 根据分类id查询菜品
* @param categoryId
* @return
*/
List<Dish> list(Long categoryId);
1.2.3 DishServiceImpl
/**
* 根据分类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
/**
* 动态条件查询菜品
* @param dish
* @return
*/
List<Dish> list(Dish dish);
1.2.5 DishMapper.xml
<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
/**
* 套餐管理
*/
@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
public interface SetmealService {
/**
* 新增套餐,同时需要保存套餐和菜品的关联关系
* @param setmealDTO
*/
void saveWithDish(SetmealDTO setmealDTO);
}
1.2.8 SetmealServiceImpl
/**
* 套餐业务实现
*/
@Service
@Slf4j
public class SetmealServiceImpl implements SetmealService {
@Autowired
private SetmealMapper setmealMapper;
@Autowired
private SetmealDishMapper setmealDishMapper;
@Autowired
private DishMapper dishMapper;
/**
* 新增套餐,同时需要保存套餐和菜品的关联关系
* @param setmealDTO
*/
@Transactional
public void saveWithDish(SetmealDTO setmealDTO) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO, setmeal);
//向套餐表插入数据
setmealMapper.insert(setmeal);
//获取生成的套餐id
Long setmealId = setmeal.getId();
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmealId);
});
//保存套餐和菜品的关联关系
setmealDishMapper.insertBatch(setmealDishes);
}
}
1.2.9 SetmealMapper
/**
* 新增套餐
* @param setmeal
*/
@AutoFill(OperationType.INSERT)
void insert(Setmeal setmeal);
1.2.10 SetmealMapper.xml
<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
/**
* 批量保存套餐和菜品的关联关系
* @param setmealDishes
*/
void insertBatch(List<SetmealDish> setmealDishes);
1.2.12 SetmealDishMapper.xml
<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
/**
* 分页查询
* @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
/**
* 分页查询
* @param setmealPageQueryDTO
* @return
*/
PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
2.2.3 SetmealServiceImpl
/**
* 分页查询
* @param setmealPageQueryDTO
* @return
*/
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
/**
* 分页查询
* @param setmealPageQueryDTO
* @return
*/
Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
2.2.5 SetmealMapper.xml
<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
/**
* 批量删除套餐
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("批量删除套餐")
public Result delete(@RequestParam List<Long> ids){
setmealService.deleteBatch(ids);
return Result.success();
}
3.2.2 SetmealService
/**
* 批量删除套餐
* @param ids
*/
void deleteBatch(List<Long> ids);
3.2.3 SetmealServiceImpl
/**
* 批量删除套餐
* @param ids
*/
@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
/**
* 根据id查询套餐
* @param id
* @return
*/
@Select("select * from setmeal where id = #{id}")
Setmeal getById(Long id);
/**
* 根据id删除套餐
* @param setmealId
*/
@Delete("delete from setmeal where id = #{id}")
void deleteById(Long setmealId);
3.2.5 SetmealDishMapper
/**
* 根据套餐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
/**
* 根据id查询套餐,用于修改页面回显数据
*
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询套餐")
public Result<SetmealVO> getById(@PathVariable Long id) {
SetmealVO setmealVO = setmealService.getByIdWithDish(id);
return Result.success(setmealVO);
}
/**
* 修改套餐
*
* @param setmealDTO
* @return
*/
@PutMapping
@ApiOperation("修改套餐")
public Result update(@RequestBody SetmealDTO setmealDTO) {
setmealService.update(setmealDTO);
return Result.success();
}
4.2.2 SetmealService
/**
* 根据id查询套餐和关联的菜品数据
* @param id
* @return
*/
SetmealVO getByIdWithDish(Long id);
/**
* 修改套餐
* @param setmealDTO
*/
void update(SetmealDTO setmealDTO);
4.2.3 SetmealServiceImpl
/**
* 根据id查询套餐和套餐菜品关系
*
* @param id
* @return
*/
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;
}
/**
* 修改套餐
*
* @param setmealDTO
*/
@Transactional
public void update(SetmealDTO setmealDTO) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO, setmeal);
//1、修改套餐表,执行update
setmealMapper.update(setmeal);
//套餐id
Long setmealId = setmealDTO.getId();
//2、删除套餐和菜品的关联关系,操作setmeal_dish表,执行delete
setmealDishMapper.deleteBySetmealId(setmealId);
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmealId);
});
//3、重新插入套餐和菜品的关联关系,操作setmeal_dish表,执行insert
setmealDishMapper.insertBatch(setmealDishes);
}
4.2.4 SetmealDishMapper
/**
* 根据套餐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
/**
* 套餐起售停售
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("套餐起售停售")
public Result startOrStop(@PathVariable Integer status, Long id) {
setmealService.startOrStop(status, id);
return Result.success();
}
5.2.2 SetmealService
/**
* 套餐起售、停售
* @param status
* @param id
*/
void startOrStop(Integer status, Long id);
5.2.3 SetmealServiceImpl
/**
* 套餐起售、停售
* @param status
* @param id
*/
public void startOrStop(Integer status, Long id) {
//起售套餐时,判断套餐内是否有停售菜品,有停售菜品提示"套餐内包含未启售菜品,无法启售"
if(status == StatusConstant.ENABLE){
//select a.* from dish a left join setmeal_dish b on a.id = b.dish_id where b.setmeal_id = ?
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
/**
* 根据套餐id查询菜品
* @param setmealId
* @return
*/
@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 功能测试
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了