苍穹外卖day03

公共字段自动填充

需求分析

在这里插入图片描述
本来是交给service的代码为entity来set公共的值,再交给数据库去处理,有公共的代码冗余,解决这个问题。
现在交给AOP切面,然后会拦截Mapper之中update或者insert的操作,来提前处理获取类似time和user的值,直接交给Mapper使用
AOP切面

方法:
自定义AutoFill,用于标识需要进行的公共字段自动填充方法
自定义切面类AutoFillAspect,统一拦截加入了AutoFill的方法,通过反射为公共字段赋值
在Mapper方法加入AutoFill注解

如果是查询操作就没有必要自动填充。

实现

自定义包com.sky.annotation和包下类AutoFill

package com.sky.annotation;



//自定义注解,用于标识某个方法要进行功能字段的自动填充处理

import com.sky.enumeration.OperationType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {

    OperationType value();	//Mapper操作方法名称的实体类


}

自定义包com.sky.aspect和包下类AutoFillAspect

package com.sky.aspect;




//自定义切面,实现公共字段自动填充


import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.asm.Advice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

@Aspect
@Component
@Slf4j//酸辣粉四斤
public class AutoFillAspect {


    //    切入点 注释是为了拦截Mapper的方法
    @Pointcut("execution(* com.sky.mapper.*.*(..))&& @annotation(com.sky.annotation.AutoFill)" )    //在mapper类中的方法,然后还需要有annotation的定义
    public void autoFillPointCut(){}

    //前置通知 在通知中进行公共字段赋值
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException {
        log.info("开始进行公共字段自动填充");

        //获取当前被拦截的方法上的数据库操作类型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //方法签名对象
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获取的方法上的注解对象
        OperationType operationType = autoFill.value();//获得数据库的操作类型

        //获取到当前被拦截方法的参数,获取实体对象比如Employee对象
        Object[] args = joinPoint.getArgs();
        if(args == null || args.length==0) {
            return;
        }
        Object entity = args[0];        //因为对象不确定就用Object接受

        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 setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser", Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);


                //通过反射为对象赋值
                setCreateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            }catch (Exception e){
                e.printStackTrace();
            }

            //为四个公共字段赋值


        }else if(operationType == OperationType.UPDATE){
            try{
                Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);

                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            }catch (Exception e){
                e.printStackTrace();
            }
        }


        //公共属性包括creatTime,createUser等
        //再说一边User是通过Treadlocal来获得的

        //根据操作类型,为对应的操作赋值
    }

}

在Mapper类中EmployMapper的update和insert方法中加入注解

@AutoFill(value=OperationType.UPDATE)

流程

开始(Start):

流程从Mapper方法的调用开始,例如EmployeeMapper.insert或EmployeeMapper.update。
拦截Mapper方法调用(Mapper Method Call):

用户调用Mapper类中的方法,触发AOP拦截。
AOP拦截调用(AOP Intercepts Call):

AOP框架通过定义的切面AutoFillAspect拦截方法调用。
检查方法上是否有@AutoFill注解。
确定操作类型(INSERT或UPDATE)。
获取方法参数(Fetch Method Arguments):

从方法参数中提取实体对象,例如Employee对象。
检查操作类型(Check OperationType):

根据操作类型进行不同的处理。
如果是INSERT操作:
设置createTime、createUser、updateTime和updateUser字段。
如果是UPDATE操作:
只设置updateTime和updateUser字段。
调用方法设置字段(Invoke Methods to Set Fields):

使用反射机制调用实体类的方法来设置字段值。
继续执行原始方法(Proceed with Original Method Execution):

在完成字段赋值后,继续执行原始的Mapper方法,如将实体保存到数据库。
结束(End):

流程结束,返回结果。

新增菜品(需要获取前端信息,插入两个表一个Dish一个Dishfavor)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

文件上传(主要是阿里OSS的配置,上传操作代码简单)

上传的文件到阿里oss之中,首先定义CommonController类来定义大家都有的操作,然后在application.yml和application-dev.yml之中配置好oss的信息,

server:
  port: 8080

spring:
  profiles:
    active: dev
  main:
    allow-circular-references: true
  datasource:
    druid:
      driver-class-name: ${sky.datasource.driver-class-name}
      url: jdbc:mysql://${sky.datasource.host}:${sky.datasource.port}/${sky.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      username: ${sky.datasource.username}
      password: ${sky.datasource.password}

mybatis:
  #mapper配置文件
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.sky.entity
  configuration:
    #开启驼峰命名
    map-underscore-to-camel-case: true

logging:
  level:
    com:
      sky:
        mapper: debug
        service: info
        controller: info

sky:
  jwt:
    # 设置jwt签名加密时使用的秘钥
    admin-secret-key: itcast
    # 设置jwt过期时间
    admin-ttl: 7200000
    # 设置前端传递过来的令牌名称
    admin-token-name: token
  alioss:
      endpoint: ${sky.alioss.endpoint}
      access-key-id: ${sky.alioss.access-key-id}
      access-key-secret: ${sky.alioss.access-key-secret}
      bucket-name: ${sky.alioss.bucket-name}

把Alioss的参数从AliOssUtil 给到我们的Controller

package com.sky.controller.admin;


import com.sky.constant.MessageConstant;
import com.sky.result.Result;
import com.sky.utils.AliOssUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.bcel.classfile.Constant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.UUID;


@RestController
@RequestMapping("/admin/common")
@Api(tags="通用接口")
@Slf4j
public class CommonController {

    @Autowired
    private AliOssUtil aliOssUtil;


    @PostMapping("/upload")
    @ApiOperation("文件上传")
    public Result<String> upload(MultipartFile file){       //参数名和前端命名保持一致
        log.info("文件上传:{}",file);
        //将文件上传到阿里云上

        try {

            String originalFilename = file.getOriginalFilename();
            //截取后缀
            String extend = originalFilename.substring(originalFilename.lastIndexOf('.'));
            //因为名字会有重复,所以我们使用UUID来防止重复的名字
            String objectName = UUID.randomUUID().toString()+ extend;

            String filePath = aliOssUtil.upload(file.getBytes(), objectName);

            return Result.success(filePath);
        } catch (IOException e) {
            return Result.error(MessageConstant.UPLOAD_FAILED);
        }


        //return Result.error(MessageConstant.UPLOAD_FAILED);



    }

}


获取到前端上传的文件,去post到oss服务器上,其中需要解决命名重复的问题,所以用uuid

菜品controller定义 (注释理解一下)

controller注释 通过注释统一交给springboot管理

@RestController // Spring MVC @RestController 是 @Controller 和 @ResponseBody 的组合注解,
@Slf4j //log
@Api(tags=“菜品相关接口”) //Swagger
@RequestMapping(“/admin/dish”) //Spring MVC

@RequestBody //Spring MVC 中的一个注解,用于将 HTTP 请求的正文(Body)转换为一个 Java 对象

package com.sky.service.impl;

@Service
@Slf4j
public class DishServiceImpl implements DishService {



    @Autowired	//Spring 框架中的一个注解,用于自动装配 Spring 容器中的 bean(组件)
    private DishMapper dishMapper;

    @Transactional  //用于声明性地管理事务。它可以应用于类或方法上,用于定义方法运行时的事务属性。在后台,Spring 使用 AOP(面向切面编程)来管理事务,这样开发者就无需手动管理事务边界,从而简化了代码。\
    public void saveWithFlavor(DishDTO dishDTO) {

        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO,dish);



    }


}

controller写完之后写service


@Service
@Slf4j
public class DishServiceImpl implements DishService {



    @Autowired //Spring 框架中的一个注解,用于自动装配 Spring 容器中的 bean(组件)
    private DishMapper dishMapper;
    @Autowired
    private DishFlavorMapper dishFlavorMapper;

    @Transactional  //用于声明性地管理事务。它可以应用于类或方法上,用于定义方法运行时的事务属性。在后台,Spring 使用 AOP(面向切面编程)来管理事务,这样开发者就无需手动管理事务边界,从而简化了代码。\
    public void saveWithFlavor(DishDTO dishDTO) {

        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO,dish);

        dishMapper.insert(dish);
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if(flavors != null && flavors.size()>0){

            dishFlavorMapper.insertBatch();

        }


    }

service里面的所有关于数据库的操作,全部使用mapper来实现

具体操作:
在成员里面进行初始化Mapper

	@Autowired //Spring 框架中的一个注解,用于自动装配 Spring 容器中的 bean(组件)
    private DishMapper dishMapper;

然后在service里面调用成员mapper的方法

dishFlavorMapper.insertBatch();

再去具体的Mapper类里去实现,可以使用注释@实现,也可以使用Mapper.xml配置文件来实现

新增菜品(和员工类似都一样,重点写mapper.xml)

useGeneratedKeys=“true” keyProperty=“id” 可以执行完insert代码之后返回id的值


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishMapper">

    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into dish (name,category_id,price,image,description,create_time,update_time,create_user,update_user,status)
        values
        (#(name),#(categoryId),#(name),#(price),#(image),#(description),#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})
    </insert>
</mapper>


		dishMapper.insert(dish);
        Long id = dish.getId();

可以使用getid获得id
重点来了 useGeneratedKeys=“true” keyProperty=“id” 可以执行完insert代码之后返回id的值

还记得AOP操作吗

@AutoFill(value = OperationType.INSERT)
    void insert(Dish dish);

在mapper的方法上面加上注释,就会自动填充公共字段了

新增口味 mapper里面

意思就是插入的是List的里面所选择的内容,区别于原来是#{name}这样的,这里是读取传入的List的内容再插入

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishFlavorMapper">

    <insert id="insert">
        insert into dish (name,category_id,price,image,description,create_time,update_time,create_user,update_user,status)
        values
        (#(name),#(categoryId),#(name),#(price),#(image),#(description),#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})
    </insert>
    <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>
</mapper>

在 MyBatis 中, 元素用于在 SQL 语句中迭代集合(如 List、Set 或数组)并生成重复的 SQL 片段。这在需要批量插入、更新或删除操作时特别有用。
元素的使用
元素有几个常用属性:
collection:指定要迭代的集合(例如 List、Set 或数组)。
item:指定在迭代过程中表示集合中每个元素的变量名称。

菜品分页查询

流程 controller增加 page方法,返回pageResult,参数是dishpageQuertDTO

	@GetMapping("/page")
    @ApiOperation("分页查询")
    public Result<PageResult>  page(DishPageQueryDTO dishPageQueryDTO){
        log.info("菜品分页查询,参数为{}",dishPageQueryDTO);
        PageResult pageResult = dishService.pageQuert(dishPageQueryDTO);


        return Result.success(pageResult);
    }

Service中使用PageHelper传入getpage和pagesize,调用mapper函数,返回total个数和List of DishVO

public PageResult pageQuert(DishPageQueryDTO dishPageQueryDTO){
        PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());
        Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);

        long total = page.getTotal();
        List<DishVO> result = page.getResult();

        return new PageResult(total,result);


    }

Mapper之中使用xml编写sql语句

现在有问题,搜索的数据有两个name,一个是dish的,一个是dishcategories的,起别名来解决

select d.* , c.name from dish d left outer join category c on d.category_id = c.id

```xml
<select id="pageQuery" resultType="com.sky.entity.Dish">
        select d.* , c.name from dish d left outer join category c on d.category_id = c.id
        <where>
            <if test="name!=null">
                and d.name like concat('%',#(name),'%')
            </if>
            <if test="categoryId  != null">
                and d.category_id like #{categoryId}
            </if>
            <if test="status!=null">
                and d.status like #{status}
            </if>
        </where>
        order by d.create_time desc
    </select>

where那部分是动态查询,是为了让管理员可以查看不同口味或者不同名字的dish

删除菜品

在这里插入图片描述
在这里插入图片描述

Controller

	@DeleteMapping
    @ApiOperation("删除菜品")
    public Result delete(@RequestParam  List<Long> ids){

        log.info("菜品批量删除:{}",ids);

        dishService.deleteBatch(ids);

        return Result.success();
    }

使用?ids=的方式连接,所以需要RequestParam ,之前写成Body了

@RequestBody
用途:用于从 HTTP 请求体中提取数据。
绑定方式:将整个请求体转换为 Java 对象。
数据格式:适用于 JSON、XML 等复杂的数据格式。
使用示例

@RequestParam
用途:用于从 URL 查询参数或表单数据中提取参数。
绑定方式:绑定到 URL 的查询参数或表单参数。
数据格式:适用于简单的键值对数据。

@PathVariable
用途:用于从 URL 路径中提取参数。
绑定方式:绑定到 URL 路径中的变量。
数据格式:适用于路径中的变量。

service

@Transactional
    public void deleteBatch(List<Long> ids){

        //判断当前菜品是否能够删除--是否在售出
        for(Long id:ids){

            Dish dish = dishMapper.getById(id);
            if(dish.getStatus() == StatusConstant.ENABLE){
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
         }


        //是否被套餐关联了
        List<Long> setmealIdsDishIds = setmealDishMapper.getSetmealIdsDishIds(ids);
        if(setmealIdsDishIds!=null&& setmealIdsDishIds.size()>0){

            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);

        }
        //删除菜品表中的菜品数据
        for(Long id:ids){
            dishMapper.deleteById(id);
            //删除菜品关联的口味数据
            dishFlavorMapper.deleteByDishId(id);
        }
        //DishMapper.delete(ids);
    }

异常有MessageConstant常量来写明白,不需要自己写
Dish dish = dishMapper.getById(id);简单的直接注释@写sql语句就行了

Mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.SetmealDishMapper">

<select id="getSetmealIdsDishIds" resultType="java.lang.Long">
    select setmeal_id  from setmeal_dish  where dish_id in
    <foreach collection="dishId"   separator="," open="(" close=")">
        #{dishId}
    </foreach>
</select>

</mapper>

@Select(“select setmea_id from setmeal_dish where dish_id in dishId”)
元素:

:这是 MyBatis 的动态 SQL 标签,用于处理集合类型的参数。它将集合中的每个元素插入到 SQL 语句中,生成一个适用于 IN 子句的列表。
collection=“dishId”:指定传入的集合参数的名称。
separator=“,”:指定集合元素之间的分隔符为逗号。
open=“(”:在生成的列表前插入一个左括号。
close=“)”:在生成的列表后插入一个右括号。

根据id回显数据

controller

	@GetMapping("/{id}")
    @ApiOperation("根据id查询菜品用于回显")
    public Result<DishVO> getById(@PathVariable Long id){
        log.info("查询菜品的id:{}",id);

        DishVO dishVO = dishService.getByIdWithFlavor(id);


        return Result.success(dishVO);
    }

@RequestBody
用途:用于从 HTTP 请求体中提取数据。
绑定方式:将整个请求体转换为 Java 对象。
数据格式:适用于 JSON、XML 等复杂的数据格式。
使用示例

@RequestParam
用途:用于从 URL 查询参数或表单数据中提取参数。
绑定方式:绑定到 URL 的查询参数或表单参数。
数据格式:适用于简单的键值对数据。

@PathVariable
用途:用于从 URL 路径中提取参数。
绑定方式:绑定到 URL 路径中的变量。
数据格式:适用于路径中的变量。

service

public DishVO getByIdWithFlavor(Long id){


        Dish dishbyId = dishMapper.getById(id);
        List<DishFlavor> dishFlavors = dishFlavorMapper.getById(id);


        DishVO dishVO  = new DishVO();
        BeanUtils.copyProperties(dishbyId,dishVO);
        dishVO.setFlavors(dishFlavors);
        
        return dishVO;
    }

Flavors是用List因为一个dish可以对应多个Flavor

修改菜品

分析

在这里插入图片描述

controller

	@PutMapping
    @ApiOperation("修改菜品")
    public Result update(@RequestBody DishDTO dishDTO){

        log.info("修改菜品:{}",dishDTO);
        dishService.updateWithFlavor(dishDTO);

        return Result.success();

    }

更新用PUT,需要所有的数据所以是@RequestBody

service

public void updateWithFlavor(DishDTO dishDTO){


        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO,dish);
        dishMapper.update(dish);
        
        //先删除口味数据,再插入口味数据
        dishFlavorMapper.deleteByDishId(dish.getId());
        List<DishFlavor> flavors = dishDTO.getFlavors();

        if(flavors != null && flavors.size()>0){

            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dish.getId());
            });

            dishFlavorMapper.insertBatch(flavors);

        }


    }

Mapper

更改dish内容,使用动态sql

  	@AutoFill(value = OperationType.UPDATE)
    void update(Dish dish);

自动填空公共字段

<update id="update" >
        update dish
        <set>
            <if test="name != null">name = #{name},</if>
            <if test="categoryId != null">category_id = #{categoryId},</if>
            <if test="price != null">price = #{price},</if>
            <if test="image != null">image = #{image},</if>
            <if test="description != null">description = #{description},</if>
            <if test="status != null">status = #{status},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="updateUser != null">update_user = #{updateUser},</if>
        </set>
        where id = #{id}
    </update>

插入口味,先删除所有,再插入返回的所有List flavor

void insertBatch(List<DishFlavor> flavors);

<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>

知识

Spring Boot 应用程序中常见的 Spring Beans 包括 @Service、@Repository、@Controller、@RestController、@Component、@Configuration、@Bean 以及 @Mapper 和 @ControllerAdvice 等

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值