企业级开发项目"苍穹外卖"(二)

添加公共通用注解

因为接下来的许多接口需要用到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("开始进行共享字段自动填充...");
    }
}
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:注意':'后面有个空格

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.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 功能测试

posted @   何平安  阅读(297)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
浏览器标题切换end
点击右上角即可分享
微信分享提示