苍穹外卖-后端学习-管理端

1.项目结构

1.项目模块

序号名称说明
1sky-take-outmaven父工程,统一管理依赖版本,聚合其他子模块
2sky-common子模块,存放公共类,例如:工具类、常量类、异常类等
3sky-pojo子模块,存放实体类、VO、DTO等
4sky-server子模块,后端服务,存放配置文件、Controller、Service、Mapper等

2.sky-common

名称说明
constant存放相关常量类
context存放上下文类
enumeration项目的枚举类存储
exception存放自定义异常类
json处理json转换的类
properties存放SpringBoot相关的配置属性类
result返回结果类的封装
utils常用工具类

3.sky-pojo  

名称说明
Entity实体,通常和数据库中的表对应
DTO数据传输对象,通常用于程序中各层之间传递数据
VO视图对象,为前端展示数据提供的对象
POJO普通Java对象,只有属性和对应的getter和setter

4.sky-server

名称说明
config存放配置类
controller存放controller类
interceptor存放拦截器类
mapper存放mapper接口
service存放service类
SkyApplication启动类

2.ThreadLocal

用户发起的每一次请求是一个线程,每个线程有一个共享的线程空间

ThreadLocal类用来提供线程内部的局部变量,不同的线程之间不会相互干扰,可以使用线程空间
使用三个方法set(),get(),remove()

本项目使用工具类封装ThreadLocal便于使用

package com.sky.context;

public class BaseContext {

    public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }

    public static Long getCurrentId() {
        return threadLocal.get();
    }

    public static void removeCurrentId() {
        threadLocal.remove();
    }

}

3.员工模块

1.员工登录

1.EmployeeController

接收用户请求->调用Service处理业务->将处理结果返回给前端

(1)Controller类上添加注解

//Spring注解,用于标记控制类
@RestController
//类上添加,整个类上的方法都被添加上url
@RequestMapping("/admin/employee")
//lombok注解,将为该类创建一个名为log的日志对象
//生成的对象log可以直接使用,调用log.debug(),log.info()进行日志输出
@Slf4j
//swagger注解,用于Controller类,对类进行说明
@Api(tags = "员工相关接口")

(2)创建EmployeeService和JwtProperties对象并添加@Autowired注解自动注入

(3)创建login方法,并添加注解

    //前端提交方式->Post,URL->/admin/employee/login
    @PostMapping("/login")
    //swagger用于生成说明方法功能的注解,若不添加在swagger中默认以方法名作为说明
    @ApiOperation("员工登录")
    //返回EmployeeLoginVO->VO是视图对象,为前端展示数据提供的对象
    //传入EmployeeLoginDTO->数据传输对象,用于程序中各层之间传递数据
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        log.info("员工登录:{}", employeeLoginDTO);

        Employee employee = employeeService.login(employeeLoginDTO);

        //登录成功后,生成jwt令牌
        //放入到token中
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);
        
        //建造者模式->相当于构造函数+set()
        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();

        return Result.success(employeeLoginVO);
    }

2.EmployeeService

处理业务

(1)在EmployeeServiceImpl类上添加@Service注解

(2)创建EmployeeMapper对象,添加@Autowired注解自动注入

(3)添加login方法

    //返回查询到的Employee实体对象
    public Employee login(EmployeeLoginDTO employeeLoginDTO) {
        String username = employeeLoginDTO.getUsername();
        String password = employeeLoginDTO.getPassword();

        //1、根据用户名查询数据库中的数据
        Employee employee = employeeMapper.getByUsername(username);

        //2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)
        if (employee == null) {
            //账号不存在
            //自定义的账号查询异常,传递自定义的信息常量->
            //public static final String ACCOUNT_NOT_FOUND = "账号不存在";
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        //密码比对
        //进行md5加密,然后再进行比对->Java提供的工具类可用于md5加密
        password =  DigestUtils.md5DigestAsHex(password.getBytes());

        if (!password.equals(employee.getPassword())) {
            //密码错误
            //自定义的密码错误异常,传递自定义的信息常量->
            //public static final String PASSWORD_ERROR = "密码错误";
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }

        if (employee.getStatus() == StatusConstant.DISABLE) {
            //账号被锁定
            //自定义的账号锁定异常,传递自定义的信息常量->
            //public static final String ACCOUNT_LOCKED = "账号被锁定";
            throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
        }

        //账号存在,密码正确,账号未锁定
        //3、返回实体对象
        return employee;
    }

3.EmployeeMapper

与数据库交互

    //与数据库交互的类使用实体类
    @Select("select * from employee where username = #{username}")
    Employee getByUsername(String username);

2.员工登出

    @PostMapping("/logout")
    @ApiOperation("员工登出")
    public Result<String> logout() {
        return Result.success();
    }

3.Jwt校验拦截器

(1)创建JwtTokenAdminInterceptor类,继承HandlerInterceptor类,添加@Component注解

(2)创建JwtProperties类,添加@Autowired注解

(3)重写preHandle方法

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取jwt令牌(Controller中创建jwt后放入到了token中)
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:", empId);
            //BaseContext为封装ThreadLocal的工具类,此方法可以将用户ID存储到ThreadLocal中
            BaseContext.setCurrentId(empId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }

4.新增员工

1.EmployeeController
创建save方法,并添加注解

    //新增员工
    @PostMapping
    @ApiOperation("新增员工")
    public Result save(@RequestBody EmployeeDTO employeeDTO){
        employeeService.save(employeeDTO);
        return Result.success();
    }

2.EmployeeService
添加save方法

    //新增员工
    //DTO用于不同层级之间传递
    @Override
    public void save(EmployeeDTO employeeDTO) {
        
        //Mapper层与数据库交互,需要使用实体类
        Employee employee = new Employee();

        //对象属性拷贝
        //将两个对象的属性根据名称拷贝赋值内容(source,target)
        BeanUtils.copyProperties(employeeDTO,employee);

        //设置状态,状态常量->可使用
        employee.setStatus(StatusConstant.ENABLE);
        //设置密码,默认123456
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());

        //设置当前创建人和修改人
        employee.setCreateUser(BaseContext.getCurrentId());
        employee.setUpdateUser(BaseContext.getCurrentId());

        employeeMapper.insert(employee);
    }

3.EmployeeMapper

    //插入用户
    @Insert("insert into sky_take_out.employee (name, username, password, phone, sex, id_number, status, create_time, update_time, create_user, update_user) " +
            "VALUES " +
            "(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})")
    void insert(Employee employee);

4.用户名重复时的异常处理

在全局异常处理器GlobalExceptionHandler(sky-server->handler->GlobalExceptionHandler)中添加exceptionHandler方法,拦截抛出的SQLIntegrityConstraintViolationException异常,并添加ExceptionHandler注解

且当message中包含Duplicate entry时提示该用户名已存在,否则抛出信息常量的未知异常

    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException exception){
        //java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'lisi' for key 'employee.idx_username'
        String message = exception.getMessage();
        if (message.contains("Duplicate entry")){
            String[] split = message.split(" ");
            String username = split[2];
            String msg = username+ MessageConstant.ALREADY_EXISTS;
            return Result.error(msg);
        }else {
            return Result.error(MessageConstant.UNKNOWN_ERROR);
        }
    }

5.分页查询

1.EmployeeController
创建pageQuery方法,并添加注解,指定URL

    @GetMapping("/page")
    @ApiOperation("员工分页查询")
    public Result<PageResult> pageQuery(EmployeePageQueryDTO employeePageQueryDTO){
        //使用自定义的PageResult类接收数据->total,总记录数;records,当前页数据集合
        PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
        return Result.success(pageResult);
    }

2.EmployeeService
添加pageQuery方法

    @Override
    public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
        //使用PageHelper插件自动分页->传递第几页和页大小
        PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
        Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
        return new PageResult(page.getTotal(),page.getResult());
    }

3.EmployeeMapper

    Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

由于分页查询是动态SQL->必须使用mapper.xml文件

在resources包内创建mapper包,包内创建EmployeeMapper.xml文件(同名)

<!--namespace->指定绑定的java文件-->
<mapper namespace="com.sky.mapper.EmployeeMapper">
    <!--
        id->指定绑定的类方法
        resultType->指定返回的参数类型
    -->
    <select id="pageQuery" resultType="com.sky.entity.Employee">
        select * from sky_take_out.employee
        <where>
            <!--判断name是否存在-->
            <if test="name!=null and name!=''">
                <!--模糊查询-->
                and name like concat('%',#{name},'%')
            </if>
        </where>
        <!--根据创建时间倒序排序-->
        order by create_time desc
    </select>
</mapper>

4.修改时间格式

由于从数据库中查询出的时间的格式不正确,需要修改时间格式,有两种方式

1.在返回数据的实体类上使用注解

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;

在需要修改的项少时可以使用,多时可以直接统一在配置处修改

2.在WebMvcConfiguration类中添加消息转化器

复写extendMessageConverters方法,扩展SpringMVC框架的消息转化器

    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //创建一个消息转换器对象
        MappingJackson2CborHttpMessageConverter converter = new MappingJackson2CborHttpMessageConverter();
        //为消息转换器设置一个对象转换器,将Java对象序列化转化为json数据
        //新建的对象转换器是自定义的
        converter.setObjectMapper(new JacksonObjectMapper());
        //将自定义的消息转换器加入容器中
        converters.add(0,converter);
    }

自定义SpringMVC配置

创建WebMvcConfiguration类继承WebMvcConfigurationSupport

类中可以自定义许多SpringMVC中的配置

(1)添加自定义拦截器->addInterceptors

        在类中创建自定义的JwtTokenAdminInterceptor拦截器对象

        使用addInterceptors方法添加拦截器和拦截的URL

    protected void addInterceptors(InterceptorRegistry registry) {
        log.info("开始注册自定义拦截器...");
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/admin/**")
                .excludePathPatterns("/admin/employee/login");
    }

(2)设置静态资源映射

    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

(3)扩展SpringMVC框架的消息转化器

7.启用禁用账号状态

1.EmployeeController
创建startOrStop方法,并添加注解,指定URL

    @PostMapping("/status/{status}")
    @ApiOperation("启动或停用员工账号")
    public Result startOrStop(@PathVariable Integer status,Long id){
        employeeService.startOrStop(status,id);
        return Result.success();
    }

@PathVariable("status")可以将URL中占位符参数绑定到方法中->

        前端URL为/admin/employee/status/1        

        方法注解为/status/{status}                

->将{}内的数据绑定到方法中->status=1

2.EmployeeService
添加startOrStop方法

    @Override
    public void startOrStop(Integer status, Long id) {
        Employee employee = Employee.builder()
                .status(status)
                .id(id)
                .createUser(BaseContext.getCurrentId())
                .build();
        employeeMapper.update(employee);
    }

3.EmployeeMapper

添加update方法,使方法可以用于多种内容的更新

        ->使用动态SQL语句,判断参数是否存在,如果存在则修改

        ->动态SQL不能使用注解SQL,在xml文件中添加SQL语句

    <update id="update" parameterType="Employee">
        update sky_take_out.employee
        <set>
            <if test="name!=null and name!=''">name = #{name},</if>
            <if test="username!=null and username!=''">username = #{username},</if>
            <if test="password!=null and password!=''">password = #{password},</if>
            <if test="phone!=null and phone!=''">phone = #{name},</if>
            <if test="sex!=null and sex!=''">sex = #{sex},</if>
            <if test="idNumber!=null and idNumber!=''">id_number = #{idNumber},</if>
            <if test="status!=null">status = #{status},</if>
            <if test="updateUser!=null">update_user = #{updateUser},</if>
            update_time = now()
        </set>
        where id=#{id}
    </update>

8.编辑员工

1.根据id查询员工信息

1.EmployeeController
创建queryById方法,并添加注解,指定URL

    @GetMapping("/{id}")
    @ApiOperation("根据id查询员工信息")
    public Result<EmployeeDTO> queryById(@PathVariable Long id){
        EmployeeDTO employeeDTO = employeeService.queryById(id);
        return Result.success(employeeDTO);
    }

2.EmployeeService
添加queryById方法

    @Override
    public EmployeeDTO queryById(Long id) {
        return employeeMapper.queryById(id);
    }

3.EmployeeMapper

    @Select("select * from employee where id = #{id}")
    EmployeeDTO queryById(Long id);

2.编辑员工信息

1.EmployeeController
创建modify方法,并添加注解,指定URL

    @PutMapping
    @ApiOperation("修改员工信息")
    public Result modify(@RequestBody EmployeeDTO employeeDTO){
        employeeService.modify(employeeDTO);
        return Result.success();
    }

2.EmployeeService
添加modify方法

    @Override
    public void modify(EmployeeDTO employeeDTO) {
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO,employee);
        employee.setCreateUser(BaseContext.getCurrentId());
        employeeMapper.update(employee);
    }

3.修改用户信息的mapper方法已存在,直接调用即可

4.菜品分类模块

1.新增菜品分类

1.新建CategoryController

(1)添加注解

@RestController
@Api(tags = "菜品分类")
@RequestMapping(("/admin/category"))

Controller应该添加的注解是@RestController而不是@Controller

(2)创建对象CategoryService,添加注解@Autowired自动注入

(3)添加save方法

    @PostMapping
    @ApiOperation("新增分类")
    public Result save(@RequestBody CategoryDTO categoryDTO){
        categoryService.save(categoryDTO);
        return Result.success();
    }

2. 新建CategoryServiceImpl类

(1)实现CategoryService接口,添加@Service注解

(2)创建对象CategoryMapper,添加注解@Autowired自动注入

(3)添加save方法

    @Override
    public void save(CategoryDTO categoryDTO) {
        Category category = new Category();
        BeanUtils.copyProperties(categoryDTO,category);
        category.setStatus(StatusConstant.DISABLE);
        category.setCreateTime(LocalDateTime.now());
        category.setUpdateTime(LocalDateTime.now());
        category.setCreateUser(BaseContext.getCurrentId());
        category.setUpdateUser(BaseContext.getCurrentId());
        categoryMapper.save(category);
    }

3.新建CategoryMapper接口

(1)添加@Mapper注解

(2)添加save方法

    @Insert("insert into sky_take_out.category (type, name, sort, status, create_time, update_time, create_user, update_user) " +
            "VALUES " +
            "(#{type},#{name},#{sort},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})")
    void save(Category category);

2.菜品分类分页查询

1.CategoryController
创建pageQuery方法,并添加注解,指定URL

    @GetMapping("/page")
    @ApiOperation("分页查询")
    public Result<PageResult> pageQuery(CategoryPageQueryDTO categoryPageQueryDTO){
        PageResult pageResult = categoryService.pageQuery(categoryPageQueryDTO);
        return Result.success(pageResult);
    }

2.CategoryService
添加pageQuery方法

    @Override
    public PageResult pageQuery(CategoryPageQueryDTO categoryPageQueryDTO) {
        PageHelper.startPage(categoryPageQueryDTO.getPage(),categoryPageQueryDTO.getPageSize());
        Page<Category> categories = categoryMapper.pageQuery(categoryPageQueryDTO);
        return new PageResult(categories.getTotal(),categories.getResult());
    }

3.CategoryMapper

    Page<Category> pageQuery(CategoryPageQueryDTO categoryPageQueryDTO);

由于pageQuery传递的参数可变,必须使用动态SQL->创建CategoryMapper.xml

将namespace指定为com.sky.mapper.CategoryMapper

添加select

    <select id="pageQuery" resultType="Category">
        select * from sky_take_out.category
        <where>
            <if test="name!=null and name!=''">
                and name like concat('%',#{name},'%')
            </if>
            <if test="type!=null">
                and type = #{type}
            </if>
        </where>
        order by sort asc,create_time desc
    </select>

3.根据id删除菜品分类

1.CategoryController
创建deleteById方法,并添加注解,指定URL

    @DeleteMapping
    @ApiOperation("删除菜品分类")
    public Result<String> deleteById(Long id){
        categoryService.deleteById(id);
        return Result.success();
    }

2.CategoryService
添加deleteById方法

    @Override
    public void deleteById(Long id) {
        if (dishMapper.queryCount(id)>0) {
            //当前分类下有菜品,不能删除
            throw new DeletionNotAllowedException("当前分类下有菜品,不能删除");
        }
        if (setMealMapper.queryCount(id)>0) {
            //当前套餐下有菜品,不能删除
            throw new DeletionNotAllowedException("当前套餐下有菜品,不能删除");
        }
        categoryMapper.deleteById(id);
    }

由于数据库中的表之间有关联关系,需要根据Category的ID判断是否存在关联,没有关联时才能删除

(1)创建SetMealMapper和DishMapper,分别添加根据category_id计算数量的方法

    @Select("select count(id) from setmeal where category_id = #{categoryId}")
    Integer queryCount(Long categoryId);
    @Select("select count(id) from dish where category_id = #{categoryId}")
    Integer queryCount(Long categoryId);

(2)在CategoryServiceImpl中创建SetMealMapper和DishMapper对象,添加@Autowired注解

当两个表中使用该id的表项都为0时才可删除,否则抛出自定义异常DeletionNotAllowedException
3.CategoryMapper

    @Delete("delete from category where id = #{id}")
    void deleteById(Long id);

4.修改菜品分类

1.CategoryController
创建modify方法,并添加注解,指定URL

    @PutMapping
    @ApiOperation("修改菜品分类")
    public Result<String> modify(@RequestBody CategoryDTO categoryDTO){
        categoryService.modify(categoryDTO);
        return Result.success();
    }

2.CategoryService
添加modify方法

    @Override
    public void modify(CategoryDTO categoryDTO) {
        Category category = new Category();
        BeanUtils.copyProperties(categoryDTO,category);
        category.setUpdateTime(LocalDateTime.now());
        category.setUpdateUser(BaseContext.getCurrentId());
        categoryMapper.update(category);
    }

3.CategoryMapper

使用动态SQL,可复用于更新数据

    void update(Category category);
    <update id="update" parameterType="Category">
        update sky_take_out.category
        <set>
            <if test="name!=null and name!=''">name=#{name},</if>
            <if test="type!=null">type=#{type},</if>
            <if test="sort!=null">sort=#{sort},</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>

5.启用禁用菜品分类

1.CategoryController
创建startOrStop方法,并添加注解,指定URL

    @PostMapping("/status/{status}")
    @ApiOperation("启用禁用分类")
    public Result<String> startOrStop(@PathVariable Integer status,Long id){
        categoryService.startOrStop(status,id);
        return Result.success("修改成功");
    }

2.CategoryService
添加startOrStop方法

    @Override
    public void startOrStop(Integer status, Long id) {
        Category category = new Category();
        category.setId(id);
        category.setStatus(status);
        categoryMapper.update(category);
    }

3.CategoryMapper

可以直接调用update的方法

6.根据类型查询菜品分类

1.CategoryController
创建queryByType方法,并添加注解,指定URL

    @GetMapping("/list")
    @ApiOperation("根据类型查询分类")
    public Result<List<Category>> queryByType(Integer type){
        List<Category> categories = categoryService.queryByType(type);
        return Result.success(categories);
    }

2.CategoryService
添加queryByType方法

    @Override
    public List<Category> queryByType(Integer type) {
        return categoryMapper.queryByType(type);
    }

3.CategoryMapper

    List<Category> queryByType(Integer type);
    <select id="queryByType" resultType="Category">
        select * from sky_take_out.category
        where status = 1
        <if test="type != null">
            and type = #{type}
        </if>
        order by sort asc,create_time desc
    </select>

5.公共字段自动填充

1.自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法

(1)新建annotation包用于存储自定义注解

(2)在annotation包中新建注解类AutoFill

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT->自定义的枚举类型
    OperationType value();
}

类上方添加的注解为固定格式

Target中的ElementType.METHOD表示应用在方法之上

2.自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值

(3)新建aspect包用于存储切面类

(4)在aspect包中新建切面类AutoFillAspect

/**
 * 自定义切面类,实现公共字段自动填充处理逻辑
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    /**
     * 切入点 需要同时满足前后两个
     * 前->mapper包中的 所有类,所有方法
     * 后->annotation中的AutoFill注解
     */
    @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){
            try {
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通过反射为对象属性赋值
                setCreateTime.invoke(entity,now);
                setUpdateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else if (operationType==OperationType.UPDATE){
            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();
            }


        }
    }
}

3.在Mapper的方法上加入AutoFill注解

在Mapper文件中使用update和insert的方法上添加@AutoFill注解

//自定义字段填充,自定义的注解,值等于枚举值INSERT
@AutoFill(value = OperationType.INSERT)

@AutoFill(value = OperationType.UPDATE)

6.阿里云OSS配置

1.自定义配置属性类AliOssProperties

//配置属性类,添加ConfigurationProperties注解
//可以自动读取配置文件中的内容并存储,需要参数名相同(可以类中使用驼峰命名,yml中使用-命名)
@Component
@ConfigurationProperties(prefix = "sky.alioss")
@Data
public class AliOssProperties {

    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;

}

添加@Component将创建对象放到容器中

添加ConfigurationProperties注解可以自动读取配置文件中的内容并存储,需要参数名相同(可以类中使用驼峰命名,yml中使用-命名)

添加@Data自动生成set()get()方法

2.在application.yml文件中添加配置属性

  alioss:
    endpoint: oss-cn-beijing.aliyuncs.com
    access-key-id: LTAI5t8mnTi4ZtfaH24bJPqY
    access-key-secret: 3v0EUA7LZHmFShPOqwGjcIHxzdLv5R
    bucket-name: dioker

3.添加配置类OssConfiguration

/**
 * 配置类:用于创建AliOssUtil对象
 */
@Configuration
@Slf4j
public class OssConfiguration {

    //添加Bean注解后,让对象在程序启动时被自动创建
    @Bean
    @ConditionalOnMissingBean
    public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
        log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties);
        return new AliOssUtil(aliOssProperties.getEndpoint()
                    , aliOssProperties.getAccessKeyId()
                    , aliOssProperties.getAccessKeySecret()
                    , aliOssProperties.getBucketName());
    }
}

4.导入工具类AliOssUtil

package com.sky.utils;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;

@Data
@AllArgsConstructor
@Slf4j
public class AliOssUtil {

    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;

    /**
     * 文件上传
     *
     * @param bytes
     * @param objectName
     * @return
     */
    public String upload(byte[] bytes, String objectName) {

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 创建PutObject请求。
            ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        //文件访问路径规则 https://BucketName.Endpoint/ObjectName
        StringBuilder stringBuilder = new StringBuilder("https://");
        stringBuilder
                .append(bucketName)
                .append(".")
                .append(endpoint)
                .append("/")
                .append(objectName);

        log.info("文件上传到:{}", stringBuilder.toString());

        return stringBuilder.toString();
    }
}

4.使用

    @Autowired
    private AliOssUtil aliOssUtil;

    /**
     * 文件名需要与前端请求保持一致->必须叫file
     * @param file
     * @return
     */
    @PostMapping("/upload")
    @ApiOperation("上传文件")
    public Result<String> upload(MultipartFile file){
        try {
            //获取原始文件名
            String originalFilename = file.getOriginalFilename();
            String objectName = "";
            if (originalFilename != null) {
                //截取原始文件名后缀
                String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
                //构造新的文件名
                objectName = UUID.randomUUID().toString()+extension;
            }
            //文件请求路径
            String filePath = aliOssUtil.upload(file.getBytes(), objectName);
            return Result.success(filePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

7.菜品管理相关接口

1.新增菜品

1.DishController

接收用户请求->调用Service处理业务->将处理结果返回给前端

(1)Controller类上添加注解

@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品管理相关接口")

(2)创建DishService对象并添加@Autowired注解自动注入

(3)创建save方法,并添加注解

    @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO){
        dishService.saveDishWithFlavor(dishDTO);
        return Result.success();
    }

2.DishService

处理业务

(1)在DishServiceImpl类上添加@Service注解

(2)创建DishMapper和DishFlavorMapper对象,添加@Autowired注解自动注入

(3)添加saveDishWithFlavor方法

    @Transactional
    @Override
    public void saveDishWithFlavor(DishDTO dishDTO) {
        //向菜品表插入数据(1条)
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);
        dish.setStatus(StatusConstant.DISABLE);
        //需要主键回显,从数据库返回新加数据自增的主键值,用于向flavors数据库中插入数据
        dishMapper.insert(dish);
        //获取insert语句生成的主键值
        Long dishId = dish.getId();
        //向口味表插入数据(0~n条数据)
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && flavors.size() > 0) {
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });
            dishFlavorMapper.insert(flavors);
        }
    }

1.本方法需要在向菜品表中插入菜品后,再向dish_flavor表中插入数据 ->

        对两个表进行数据操作,应使用@Transaction注解

@Transaction注解

数据库事务: 当一个方法中需要对多个数据库表进行更新操作时,应该使用 @Transactional 注解来确保所有的操作都能够成功提交或回滚,从而保证数据的一致性和完整性。

业务逻辑: 当用户下单时,需要同时更新用户的账户信息和订单信息。使用 @Transactional 注解可以确保这些操作在同一个事务中执行

缓存更新: 在更新缓存时,需要确保缓存和数据库的一致性。使用 @Transactional 注解可以确保在更新数据库之前,缓存已经被更新或者被清空。

消息队列: 在使用消息队列处理业务时,需要确保消息被正确处理或者回滚。使用 @Transactional 注解可以保证在处理消息时,消息和数据库的操作是在同一个事务中执行的

需要在Application类上先使用@EnableTransactionManagement开启注解方式的事务管理

3.Mapper

2.insert语句从数据库返回值

向dish_flavor表插入数据时需要使用dish新生成的id值作为逻辑外键 ->

        由于id值为数据库自增,无法从前端得到,可以在向数据库插入数据时返回参数->

        ->必须使用xml文件,在select操作处添加参数 useGeneratedKeys="true" keyProperty="id"

        useGeneratedKeys =true 表示插入数据之后返回主键值

        keyproperty = ""        指明数据库中返回的主键值赋值给实体类中的哪个属性(赋值给传递过来的实体类),之后可以在Service中直接使用get()方法调用

切记:不是方法返回值,而是赋值给对象参数

    @AutoFill(OperationType.INSERT)
    void insert(Dish dish);
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into sky_take_out.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>

 3.insert语句插入多条数据

向dish_flavor表插入的数据为0~n条(前段的DTO传入后端的flavors是一个List集合)

        Service向Mapper传递的数据也为List集合 ->

        DishFlavorMapper中操作数据库的方法必须使用动态SQL,使用foreach遍历集合

    <!--
        批量插入数据 传入的是集合对象,使用动态SQL foreach遍历
        collection值需要与传过来的参数名一致
        item为循环中的自定义变量值
        separator表示语句之间用逗号分隔
    -->
    <insert id="insert">
        insert into sky_take_out.dish_flavor
        (dish_id, name, value) VALUES
        <foreach collection="dishFlavors" item="flavor" separator=",">
            (#{flavor.dishId},#{flavor.name},#{flavor.value})
        </foreach>
    </insert>

2.菜品分页查询

1.DishController
创建pageQuery方法,并添加注解,指定URL

    @GetMapping("/page")
    @ApiOperation("菜品分页查询")
    public Result<PageResult> pageQuery(DishPageQueryDTO dishPageQueryDTO){
        PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
        return Result.success(pageResult);
    }

2.DishService
添加pageQuery方法

    @Override
    public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
        PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());
        Page<DishVO> dishes = dishMapper.pageQuery(dishPageQueryDTO);
        return new PageResult(dishes.getTotal(),dishes.getResult());
    }

3.DishMapper

    Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);
    <select id="pageQuery" resultType="com.sky.vo.DishVO">
        <!-- 
            必须起别名,不然框架对应不上属性,因为是根据属性名对应的,如果不起别名会返回name
            因为数据库中的表项与DishVO中的属性值不同,设置别名让返回的值与DishVO中的属性值相同
        -->
        SELECT sky_take_out.dish.*,sky_take_out.category.name as categoryName
        FROM sky_take_out.dish, sky_take_out.category
        <where>
            dish.category_id=category.id
            <if test="name!=null and name!=''">
                and dish.name like concat('%',#{name},'%')
            </if>
            <if test="categoryId!=null">
                and dish.category_id = #{categoryId}
            </if>
            <if test="status!=null">
                and dish.status = #{status}
            </if>
        </where>
        order by dish.create_time desc
    </select>

3.菜品批量删除

1.DishController
创建delete方法,并添加注解,指定URL

    @DeleteMapping
    @ApiOperation("菜品批量删除")
    public Result<String> delete(@RequestParam List<Long> ids){
        dishService.delete(ids);
        return Result.success();
    }

2.DishService

(1)创建SetMealMapper对象,添加@Autowired注解自动注入
(2)添加delete方法

    @Override
    public void delete(List<Long> ids) {
        List<Dish> dishes = dishMapper.queryById(ids);
        dishes.forEach(dish -> {
            //菜品状态,如果为启动状态->抛异常,不能删除
            if (dish.getStatus() == StatusConstant.ENABLE) {
                throw new DeletionNotAllowedException(dish.getName() + ":" + MessageConstant.DISH_ON_SALE);
            }
            //判断菜品是否在套餐中,如果在套餐中->抛异常,不能删除
            List<Long> setMealIds = setMealMapper.getSetMealIdsByDishId(dish.getId());
            if (setMealIds != null && setMealIds.size() > 0) {
                throw new DeletionNotAllowedException(dish.getName() + ":" + MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
            }
        });
        //删除菜品表中的菜品数据
        dishMapper.delete(ids);
        //删除菜品时同时删除 菜品口味
        dishFlavorMapper.deleteByDishIds(ids);
    }

3.在DishMapper中添加方法,传入菜品ids的集合,返回菜品类的集合

List<Dish> queryById(List<Long> ids);
    <!--查询所有ids中的数据-->
    <select id="queryById" resultType="com.sky.entity.Dish">
        select * from sky_take_out.dish where id in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </select>

4.在SetMealMapper中添加方法,返回套餐ID的集合

    //根据菜品Id中获取套餐Id
    List<Long> getSetMealIdsByDishId(Long dishId);

在SetMealMapper.xml中添加select

    <!--根据菜品Id中获取套餐Id-->
    <select id="getSetMealIdsByDishId" resultType="java.lang.Long">
        select setmeal_id from sky_take_out.setmeal_dish where dish_id = #{dishId}
    </select>

5.在DishMapper中添加方法,删除ids中所有数据

    void delete(List<Long> ids);
    <!--删除菜品-->
    <delete id="delete">
        delete from sky_take_out.dish where id in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </delete>

6.在DishMapper中添加方法,删除ids中所有数据

void deleteByDishIds(List<Long> ids);
    <!--删除DishId存在ids中的数据-->
    <delete id="deleteByDishIds">
        delete from sky_take_out.dish_flavor where dish_id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>

3.根据ID查询菜品

1.DishController
创建queryById方法,并添加注解,指定URL

    @GetMapping("/{id}")
    @ApiOperation("根据ID查询菜品")
    public Result<DishVO> queryById(@PathVariable Long id){
        DishVO dishVO = dishService.queryById(id);
        return Result.success(dishVO);
    }

2.DishService
添加queryById方法

    @Transactional
    @Override
    public DishVO queryById(Long id) {
        //从菜品表中获得菜品数据,并拷贝数据给VO类
        List<Long> ids = new ArrayList<>();
        ids.add(id);
        List<Dish> dishes = dishMapper.queryById(ids);
        Dish dish = dishes.get(0);
        DishVO dishVO = new DishVO();
        BeanUtils.copyProperties(dish,dishVO);
        //从菜品口味表中获得菜品对应的口味数据,并拷贝数据给VO类
        List<DishFlavor> dishFlavors = dishFlavorMapper.queryById(dish.getId());
        dishVO.setFlavors(dishFlavors);
        BeanUtils.copyProperties(dish,dishFlavors);
        return dishVO;
    }

返回的是前端页面展示需要的类DishVO,包含菜品存储在数据库Dish中的基本信息及存储在DishCategory中的口味信息

在DishCategory中的口味信息需要使用逻辑外键dish_id进行返回,返回结果为List集合

3.DishMapper

    List<Dish> queryById(List<Long> ids);
    <!--查询所有ids中的数据-->
    <select id="queryById" resultType="com.sky.entity.Dish">
        select * from sky_take_out.dish where id in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </select>

4.DishFlavourMapper

    @Select("select * from sky_take_out.dish_flavor where dish_id = #{id}")
    List<DishFlavor> queryById(Long id);

4.修改菜品及菜品口味

1.DishController
创建modify方法,并添加注解,指定URL

    @PutMapping
    @ApiOperation("修改菜品及菜品口味")
    public Result<String> modify(@RequestBody DishDTO dishDTO){
        dishService.modify(dishDTO);
        return Result.success();
    }

2.DishService
添加modify方法

    @Transactional
    @Override
    public void modify(DishDTO dishDTO) {
        //从DTO中获取Dish的数据修改
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO,dish);
        dishMapper.update(dish);
        //删除原来的Dish_Flavour数据
        List<Long> ids = new ArrayList<>();
        ids.add(dishDTO.getId());
        dishFlavorMapper.deleteByDishIds(ids);
        //新增新的Dish_Flavour口味
        List<DishFlavor> dishFlavors = dishDTO.getFlavors();
        dishFlavors.forEach(dishFlavor -> {
            dishFlavor.setDishId(dishDTO.getId());
        });
        dishFlavorMapper.insert(dishFlavors);
    }

要想修改Dish_Flavour中的数据无法进行定位修改,若要修改菜品关联的口味数据,只能根据菜品ID将原来关联的菜品ID都删除,然后重新插入新的菜品口味数据
3.DishMapper

    @AutoFill(OperationType.UPDATE)
    void update(Dish dish);
    <!--修改菜品-->
    <update id="update">
        update sky_take_out.dish set
        <if test="name!=null and name!=''">name=#{name},</if>
        <if test="image!=null and image!=''">image=#{image},</if>
        <if test="price!=null">price=#{price},</if>
        <if test="status!=null">status=#{status},</if>
        <if test="categoryId!=null">category_id=#{categoryId},</if>
        <if test="description!=null and description!=''">description=#{description},</if>
        update_user = #{updateUser},
        update_time = #{updateTime}
        where id = #{id}
    </update>

4.DishFlavourMapper

    void deleteByDishIds(List<Long> ids);
    <!--删除DishId存在ids中的数据-->
    <delete id="deleteByDishIds">
        delete from sky_take_out.dish_flavor where dish_id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>
    void insert(List<DishFlavor> dishFlavors);
    <!--
        批量插入数据 传入的是集合对象,使用动态SQL foreach遍历
        collection值需要与传过来的参数名一致
        item为循环中的自定义变量值
        separator表示语句之间用逗号分隔
    -->
    <insert id="insert">
        insert into sky_take_out.dish_flavor
        (dish_id, name, value) VALUES
        <foreach collection="dishFlavors" item="flavor" separator=",">
            (#{flavor.dishId},#{flavor.name},#{flavor.value})
        </foreach>
    </insert>

5.起售或停售菜品

1.DishController
创建startOrStop方法,并添加注解,指定URL

    @PostMapping("/status/{status}")
    @ApiOperation("启售或停售菜品")
    public Result startOrStop(@PathVariable Integer status,Long id){
        dishService.startOrStop(status,id);
        return Result.success();
    }

2.DishService
添加startOrStop方法

    @Override
    public void startOrStop(Integer status, Long id) {
        Dish dish = new Dish();
        dish.setStatus(status);
        dish.setId(id);
        dishMapper.update(dish);
    }

update方法已写,直接调用即可

6.根据分类ID查询菜品

1.DishController
创建queryByCategoryId方法,并添加注解,指定URL

    @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
    public Result<List<Dish>> queryByCategoryId(Long categoryId){
        List<Dish> dishes = dishService.list(categoryId);
        return Result.success(dishes);
    }

2.DishService
添加queryByCategoryId方法

    @Override
    public List<Dish> list(Long categoryId) {
        return dishMapper.queryByCategoryId(categoryId);
    }

3.DishMapper

    @Select("select * from sky_take_out.dish where category_id=#{categoryId}")
    List<Dish> queryByCategoryId(Long categoryId);

8.套餐管理相关接口

1.新增套餐及菜品的关联关系

1.SetmealController

接收用户请求->调用Service处理业务->将处理结果返回给前端

(1)Controller类上添加注解

@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐相关接口")
@Slf4j

(2)创建SetmealService对象并添加@Autowired注解自动注入

(3)创建save方法,并添加注解

    @PostMapping
    @ApiOperation("新增套餐")
    public Result save(@RequestBody SetmealDTO setmealDTO) {
        setmealService.saveWithDish(setmealDTO);
        return Result.success();
    }

2.SetmealService

处理业务

(1)在SetmealServiceImpl类上添加@Service注解

(2)创建SetmealMapper和SetmealDishMapper对象,添加@Autowired注解自动注入

(3)添加save方法

    @Transactional
    @Override
    public void saveWithDish(SetmealDTO setmealDTO) {
        Setmeal setmeal = new Setmeal();
        BeanUtils.copyProperties(setmealDTO,setmeal);
        //向套餐表中插入数据,获取生成的套餐ID
        setmealMapper.insert(setmeal);
        //将获得套餐ID值赋值给连接类
        List<SetmealDish> setmealDishes =  setmealDTO.getSetmealDishes();
        setmealDishes.forEach(setmealDish -> {
            setmealDish.setSetmealId(setmeal.getId());
            log.info(setmealDish.toString());
        });
        //保存套餐和菜品的关联关系
        setmealDishMapper.insert(setmealDishes);
    }

除了向Setmeal表中添加套餐数据外,还需要向setmeal_dish表中添加套餐和菜品的关联关系

其中DTO中包含Setmeal的数据和SetmealDish的数据

新建与Setmeal表关联的Setmeal类和与SetmealDish表关联的SetmealDish类

给类赋值后分别使用对应的Mapper向表中插入数据

3.SetmealMapper

与数据库交互

    @AutoFill(OperationType.INSERT)
    void insert(Setmeal setmeal);
    <!--向套餐表中插入数据,获取生成的套餐ID-->
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into sky_take_out.setmeal
        (category_id, name, price, description, image, create_time, update_time, create_user, update_user)
        VALUES (#{categoryId}, #{name}, #{price}, #{description}, #{image}, #{createTime}, #{updateTime}, #{createUser},
                #{updateUser})
    </insert>

4.SetmealDishMapper

    void insert(List<SetmealDish> setmealDishes);
    <!--
        保存套餐和菜品的关联关系(多条)
        foreach标签循环处只写了collection项(集合)少写了item项(循环项)
        且使用#{}指定属性时需要用item项指定(setmealDish.SetmealId)
    -->
    <insert id="insert">
        insert into sky_take_out.setmeal_dish
        (setmeal_id, dish_id, name, price, copies)
        VALUES
        <foreach collection="setmealDishes" item="setmealDish" separator=",">
            (#{setmealDish.setmealId},#{setmealDish.dishId},#{setmealDish.name},#{setmealDish.price},#{setmealDish.copies})
        </foreach>
    </insert>

2.套餐分页查询

1.SetmealController
创建pageQuery方法,并添加注解,指定URL

    @GetMapping("/page")
    @ApiOperation("套餐分页查询")
    public Result<PageResult> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO){
        PageResult result = setmealService.pageQuery(setmealPageQueryDTO);
        return Result.success(result);
    }

2.SetmealService
添加pageQuery方法

    @Override
    public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {
        PageHelper.startPage(setmealPageQueryDTO.getPage(),setmealPageQueryDTO.getPageSize());
        Page<SetmealVO> setmealVOs = setmealMapper.pageQuery(setmealPageQueryDTO);
        return new PageResult(setmealVOs.getTotal(),setmealVOs.getResult());
    }

3.SetmealMapper

    Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
    <!--
        套餐分页查询
        设置返回参数别名,返回的参数名与resultType指定的类对应
        concat用,连接而不是用+连接
    -->
    <select id="pageQuery" resultType="com.sky.vo.SetmealVO">
        select setmeal.*,category.name as categoryName from sky_take_out.setmeal,sky_take_out.category
        where
        setmeal.category_id=category.id
        <if test="name!=null and name!=''">and setmeal.name like concat('%',#{name},'%')</if>
        <if test="categoryId!=null">and setmeal.category_id = #{categoryId}</if>
        <if test="status!=null">and setmeal.status = #{status}</if>
        order by setmeal.create_time desc
    </select>

3.批量删除套餐

1.SetmealController
创建delete方法,并添加注解,指定URL

    @DeleteMapping
    @ApiOperation("批量删除套餐")
    public Result delete(@RequestParam List<Long> ids){
        setmealService.deleteBatch(ids);
        return Result.success();
    }

2.SetmealService
添加delete方法

    @Transactional
    public void deleteBatch(List<Long> ids) {
        ids.forEach(id -> {
            Setmeal setmeal = setmealMapper.queryById(id);
            if(StatusConstant.ENABLE == setmeal.getStatus()){
                //起售中的套餐不能删除
                throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);
            }
        });
        //删除套餐表中的数据
        setmealMapper.deleteById(ids);
        //删除套餐菜品关系表中的数据
        setmealDishMapper.deleteBySetmealId(ids);
    }

在删除前先判断套餐的状态,若为起售中则抛出异常,提示不能删除

若判断都能删除后,则删除套餐中的数据,同时删除套餐关系表中的数据
3.SetmealMapper

    void deleteById(List<Long> ids);
    <!--批量删除套餐-->
    <delete id="deleteById">
        delete from sky_take_out.setmeal
        where id in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </delete>

4.SetmealDishMapper

    void deleteBySetmealId(List<Long> ids);
    <!--批量删除套餐和菜品的关联关系-->
    <delete id="deleteBySetmealId">
        delete from sky_take_out.setmeal_dish
        where setmeal_id in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </delete>

4.根据ID查询套餐

1.SetmealController
创建queryById方法,并添加注解,指定URL

    @GetMapping("/{id}")
    @ApiOperation("根据ID查询套餐")
    public Result<SetmealVO> queryById(@PathVariable Long id){
        SetmealVO setmealVO = setmealService.queryById(id);
        return Result.success(setmealVO);
    }

2.SetmealService
添加queryById方法

    @Transactional
    @Override
    public SetmealVO queryById(Long id) {
        SetmealVO setmealVO = new SetmealVO();
        Setmeal setmeal = setmealMapper.queryById(id);
        BeanUtils.copyProperties(setmeal,setmealVO);
        List<SetmealDish> setmealDishes = setmealDishMapper.queryBySetmealId(id);
        setmealVO.setSetmealDishes(setmealDishes);
        return setmealVO;
    }

返回传递给前端的SetmealVO类

先在setmeal表中查询套餐数据

再在setmeal_dish表中查询关联数据
3.SetmealMapper

    @Select("select * from sky_take_out.setmeal where id=#{id}")
    Setmeal queryById(Long id);

4.SetmealDishMapper

    @Select("select * from sky_take_out.setmeal_dish where setmeal_id = #{setmealId}")
    List<SetmealDish> queryBySetmealId(Long setmealId);

5.修改套餐数据

1.SetmealController
创建modify方法,并添加注解,指定URL

    @PutMapping
    @ApiOperation("修改套餐数据")
    public Result modify(@RequestBody SetmealDTO setmealDTO){
        setmealService.modify(setmealDTO);
        return Result.success();
    }

2.SetmealService
添加modify方法

    @Transactional
    @Override
    public void modify(SetmealDTO setmealDTO) {
        Setmeal setmeal = new Setmeal();
        BeanUtils.copyProperties(setmealDTO,setmeal);

        //修改套餐数据
        setmealMapper.update(setmeal);

        //更新不了,删了
        List<Long> ids = new ArrayList<>();
        ids.add(setmeal.getId());
        setmealDishMapper.deleteBySetmealId(ids);

        //重新添加新的 套餐和菜品的关联数据
        List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
        setmealDishes.forEach(setmealDish -> {
            setmealDish.setSetmealId(setmeal.getId());
        });
        setmealDishMapper.insert(setmealDishes);
    }

先修改套餐数据
数据库Setmeal_Dish表中的数据无法进行定位修改,若要修改套餐和菜品关联数据,只能根据套餐ID将原来关联的菜品ID都删除,然后重新插入新的数据 

3.SetmealMapper

    @AutoFill(OperationType.UPDATE)
    void update(Setmeal setmeal);
    <update id="update">
        update sky_take_out.setmeal
        <set>
            <if test="categoryId!=null">category_id=#{categoryId},</if>
            <if test="name!=null and name!=''">name=#{name},</if>
            <if test="price!=null">price=#{price},</if>
            <if test="status!=null">status=#{status},</if>
            <if test="description!=null and description!=''">description=#{description},</if>
            <if test="image!=null and image!=''">image=#{image},</if>
            <if test="updateTime!=null">update_time=#{updateTime},</if>
            <if test="updateUser!=null">update_user=#{updateUser},</if>
        </set>
        where id = #{id}
    </update>

4.SetmealDishMapper

    void deleteBySetmealId(List<Long> ids);
    <!--批量删除套餐和菜品的关联关系-->
    <delete id="deleteBySetmealId">
        delete from sky_take_out.setmeal_dish
        where setmeal_id in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </delete>

6.起售停售套餐

1.SetmealController
创建startOrStop方法,并添加注解,指定URL

    @PostMapping("/status/{status}")
    public Result startOrStop(@PathVariable Integer status,Long id){
        setmealService.startOrStop(status,id);
        return Result.success();
    }

2.SetmealService
添加startOrStop方法

    @Override
    public void startOrStop(Integer status, Long id) {

        //停售
        if (status==StatusConstant.DISABLE){
            Setmeal setmeal = new Setmeal();
            setmeal.setId(id);
            setmeal.setStatus(status);
            setmealMapper.update(setmeal);
            return;
        }else {
            //起售
            //查setmeal_dish表->根据setmeal_id查询,要得到所有dish_id
            List<SetmealDish> setmealDishes = setmealDishMapper.queryBySetmealId(id);
            List<Long> dishIds = new ArrayList<>();
            setmealDishes.forEach(setmealDish -> {
                dishIds.add(setmealDish.getDishId());
            });
            //查dish表->根据id查询,要得到所有status
            List<Dish> dishes = dishMapper.queryById(dishIds);
            //若status中包含0->抛异常
            dishes.forEach(dish -> {
                if (dish.getStatus()==StatusConstant.DISABLE){
                    throw new SetmealEnableFailedException(MessageConstant.SETMEAL_ENABLE_FAILED);
                }
            });
        }

        //更改
        Setmeal setmeal = new Setmeal();
        setmeal.setId(id);
        setmeal.setStatus(status);
        setmealMapper.update(setmeal);
    }

套餐停售可以直接操作,

起售需要进行判断套餐中是否有停售中的菜品,若包含停售中的菜品,则不能起售

不包含停售的菜品时才可以起售

方法都已经包含,直接调用即可

9.Redis

1.数据类型

1.String(字符串)

2.List(列表)->类似于LinkedList

3.Hash(哈希表)->类似于HashMap

4.Set(集合)->类似于HashSet

5.SortedSet(有序集合)->根据分数升序排序,没有重复元素

2.数据结构

10.店铺管理相关接口

1.店铺端

1.ShopController
(1)Controller类上添加注解

//自定义spring创建类时的名称,否则会默认以类名命名
//有同名的类时会提示无法创建报错
@Slf4j
@RestController("adminShopController")
@Api(tags = "店铺相关接口")
@RequestMapping("/admin/shop")

自定义spring创建类时的名称,否则会默认以类名命名,有同名的类时会提示无法创建报错
(2)创建RedisTemplate对象并添加@Autowired注解自动注入

需要引入Redis的依赖->阿里云OSS依赖已经包含Redis依赖,不需要再次引入可以直接创建RedisTemplate对象

1.设置店铺营业状态

创建setStatus方法,并添加注解

直接调用redisTemplate提供的方法,向Redis中添加数据

    @PutMapping("/{status}")
    @ApiOperation("设置店铺营业状态")
    public Result setStatus(@PathVariable Integer status){
        //输出日志,输出店铺的营业状态
        log.info("设置店铺的营业状态为: {}",status==1?"营业中":"打烊中");
        //向Redis中添加数据,key值为静态常量,状态从url中获取
        redisTemplate.opsForValue().set(KEY,status);
        return Result.success();
    }

2.获取店铺营业状态

创建setStatus方法,并添加注解

直接调用redisTemplate提供的方法,从Redis中获取数据

    @GetMapping("/status")
    @ApiOperation("获取店铺营业状态")
    public Result<Integer> getStatus(){
        //根据key值获取的value值,返回的是object对象,需要强制转换
        Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
        //输出日志,输出店铺的营业状态
        log.info("设置店铺的营业状态为: {}",status==1?"营业中":"打烊中");
        return Result.success(status);
    }

2.用户端

1.获取店铺营业状态

1.ShopController
(1)Controller类上添加注解

//自定义spring创建类时的名称,否则会默认以类名命名
//有同名的类时会提示无法创建报错
@Slf4j
@RestController("userShopController")
@Api(tags = "店铺相关接口")
@RequestMapping("/user/shop")

自定义spring创建类时的名称,否则会默认以类名命名,有同名的类时会提示无法创建报错
(2)创建RedisTemplate对象并添加@Autowired注解自动注入->可以直接创建RedisTemplate对象
(3)创建getStatus方法,并添加注解

    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("/status")
    @ApiOperation("获取店铺营业状态")
    public Result<Integer> getStatus(){
        Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
        log.info("设置店铺的营业状态为: {}",status==1?"营业中":"打烊中");
        return Result.success(status);
    }

将店铺的营业状态存储到Redis数据库中后, 可以直接调用RedisTemplate,从Redis中获取到存储的数据->即店铺营业状态

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值