Day3 苍穹外卖项目 公共字段自动填充(AOP)、文件上传、新增菜品、菜品分页查询、删除菜品、修改菜品

目录

1.公共字段自动填充

1.1 问题分析

1.2 实现思路

1.3 代码开发

1.3.1 自定义注解 AutoFill

1.3.2 自定义切面类 AutoFillAspect.java

1.3.3 在Mapper接口的方法上加入 AutoFill 注解

1.4 功能测试

1.5 代码提交

2.新增菜品

2.1 需求分析与设计

2.1.1 产品原型

2.1.2 接口设计

2.1.3 表设计

2.2 代码开发

2.2.1 文件上传实现

2.2.1.1 定义OSS相关配置

2.2.1.2 读取OSS配置

2.2.1.3 生成OSS工具类对象

2.2.1.4 定义文件上传接口

2.2.2 新增菜品实现

2.2.2.1 设计DTO类

2.2.2.2 Controller层

2.2.2.3 Service层接口

2.2.2.4 Service层实现类

2.2.2.5 Mapper层

2.3 功能测试

2.4 代码提交

3.菜品分页查询

3.1 需求分析和设计

3.1.1 产品原型

3.1.2 接口设计

3.2 代码开发

3.2.1 设计DTO类

3.2.2 设计VO类

3.2.3 Controller层

3.2.4 Service层接口

3.2.5 Service层实现类

3.2.6 Mapper层

3.3 功能测试

3.3.1 接口文档测试

4.删除菜品

4.1 需求分析和设计

4.1.1 产品原型

4.1.2 接口设计

4.1.3 表设计

4.2 代码开发

4.1.2 Controller层

4.2.2 Service层接口

4.2.3 Service层实现类

4.2.4 Mapper层

4.3 功能测试

4.4 代码提交

5.修改菜品

5.1 需求分析和设计

5.1.1 产品原型

5.1.2 接口设计

5.2 代码开发

5.2.1 根据id查询菜品实现

5.2.1.1 Controller层

5.2.1.2 Service层接口

5.2.1.3 Service层实现类

5.2.1.4 Mapper层

5.2.2 修改菜品实现

5.2.2.1 Controller层

5.2.2.2 Service层接口

5.2.2.3 Service层实现类

5.2.2.4 Mapper层

5.3 功能测试

5.4 代码提交


1.公共字段自动填充

1.1 问题分析

后台系统的员工管理功能分类管理功能的开发,在新增员工或者新增分类时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工或者编辑菜品分类时需要设置修改时间、修改人等字段。这些字段属于公共字段,也就是也就是在我们的系统中很多表中都会有这些字段,如下:

而针对于这些字段,我们的赋值方式为:

1). 在新增数据时, 将createTime、updateTime 设置为当前时间, createUser、updateUser设置为当前登录用户ID。

2). 在更新数据时, 将updateTime 设置为当前时间, updateUser设置为当前登录用户ID。

如果都按照上述的操作方式来处理这些公共字段, 需要在每一个业务方法中进行操作, 编码相对冗余、繁琐,那能不能对于这些公共字段在某个地方统一处理,来简化开发呢?

答案:可以的,我们使用AOP切面编程,实现功能增强,来完成公共字段自动填充功能。

1.2 实现思路

在实现公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。

实现步骤:

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

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

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

技术点:枚举、注解、AOP、反射。

原理:切入点指定拦截的路径,拦截的是Mapper层的所有方法,并且方法上还需要有@AutoFill注解才会被拦截,切面类的通知设置为前置通知@Before,在执行Mapper层对应的方法会先执行前置通知里面的代码。因此公共字段可以在这里实现统一封装。

1.3 代码开发

1.3.1 自定义注解 AutoFill

进入到sky-server模块,创建com.sky.annotation包。

package com.sky.annotation;

/**
 * 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
 */
// ElementType.METHOD该注解只能在方法上使用
@Target(ElementType.METHOD)
// 生命周期
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT
    OperationType value();
}

其中OperationType已在sky-common模块中定义枚举类型:

package com.sky.enumeration;

/**
 * 数据库操作类型
 */
public enum OperationType {

    /**
     * 更新操作
     */
    UPDATE,

    /**
     * 插入操作
     */
    INSERT
}

1.3.2 自定义切面类 AutoFillAspect.java

在sky-server模块,创建com.sky.aspect包。

package com.sky.aspect;

/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
// 定义该类为切面类
@Aspect
// 该类交给Spring管理
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 切入点(要切入的某个类的某个方法)
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

    /**
     * 前置通知,在通知中进行公共字段的赋值(操作数据库之前执行)
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        /重要
        //可先进行调试,是否能进入该方法 提前在mapper方法添加AutoFill注解
        log.info("开始进行公共字段自动填充...");

    }
}

完善自定义切面 AutoFillAspect 的 autoFill 方法

package com.sky.aspect;

/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 切入点
     */
    @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){
            //为4个公共字段赋值
            try {
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, 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){
            //为2个公共字段赋值
            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();
            }
        }
    }
}

1.3.3 在Mapper接口的方法上加入 AutoFill 注解

CategoryMapper为例,分别在新增和修改方法添加@AutoFill()注解,也需要EmployeeMapper做相同操作:

package com.sky.mapper;

@Mapper
public interface CategoryMapper {
    /**
     * 插入数据
     * @param category
     */
    @Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
            " VALUES" +
            " (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
    @AutoFill(value = OperationType.INSERT)
    void insert(Category category);
    /**
     * 根据id修改分类
     * @param category
     */
    @AutoFill(value = OperationType.UPDATE)
    void update(Category category);

}

同时,将业务层为公共字段赋值的代码注释掉。

1). 将员工管理的新增和编辑方法中的公共字段赋值的代码注释。

2). 将菜品分类管理的新增和修改方法中的公共字段赋值的代码注释。

1.4 功能测试

修改员工信息:

添加员工信息:

分类管理的测试也是类似的。

1.5 代码提交

2.新增菜品

2.1 需求分析与设计

2.1.1 产品原型

后台系统中可以管理菜品信息,通过 新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片。

新增菜品原型:

当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。

业务规则:

  • 菜品名称必须是唯一的

  • 菜品必须属于某个分类下,不能单独存在

  • 新增菜品时可以根据情况选择菜品的口味

  • 每个菜品必须对应一张图片

2.1.2 接口设计

根据上述原型图先粗粒度设计接口,共包含3个接口。

接口设计:

  • 根据类型查询分类(已完成)(显示下拉框)

  • 文件上传

  • 新增菜品(有可能涉及到两个表 dish表和dish_flavor表)

    • 注意事项:要拿到刚添加菜品的主键,设置给dish_flavor表的dish_id属性。

    •  

      涉及到多张表要开启事务。

      @Transactional //dishServiceImpl的新增方法加上
      @EnableTransactionManagement //开启注解方式的事务管理

接下来细粒度分析每个接口,明确每个接口的请求方式、请求路径、传入参数和返回值。

1. 根据类型查询分类(已完成)

​​​​​​​

2. 文件上传

3. 新增菜品

2.1.3 表设计

通过原型图进行分析:

dish表:  

<

### 苍穹外卖添加菜品功能为空解决方案 #### 一、确认前端表单提交数据有效性 确保前端页面中的新增菜品表单能够正常收集并发送所需的数据到服务器。这包括但不限于菜品名称、价格、描述以及所属分类等字段的信息验证逻辑[^1]。 #### 二、检查后端接收参数配置 核实`DishController`控制器内处理POST请求的方法是否正确设置了接受来自客户端传递过来的对象属性映射关系,特别是对于文件类型的参数如菜品图片的上传支持。 ```java @PostMapping("/add") public R<String> add(@RequestBody Dish dish){ log.info(dish.toString()); service.save(dish); return R.success("新增菜品成功"); } ``` 注意上述代码片段中使用了`@RequestBody`注解用于绑定HTTP请求体内的JSON对象至Java实体类实例;如果涉及到多部分形式数据(multipart/form-data),则应调整为相应的MultipartFile类型变量来获取图像资源。 #### 三、数据库交互操作审查 深入探究持久化层即DAO/Repository组件内部实现细节,保证SQL语句书写无误,并且与实际使用的RDBMS版本兼容良好。另外还需关注ORM框架(例如MyBatis)所定义的结果集映射规则是否准确反映了目标表格结构特征。 #### 四、日志记录排查异常情况 启用详细的运行时日志级别以便捕捉任何潜在错误提示信息,这对于定位具体问题所在位置非常有帮助。比如可以通过设置logback.xml或其他相应配置文件里的logger节点达到此目的: ```xml <configuration> <!--其他配置项--> <logger name="com.example.controller" level="DEBUG"/> </configuration> ``` 以上措施有助于全面了解整个流程执行过程中遇到的各种状况,从而快速找出导致“添加菜品”功能失效的根本原因并加以修复。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值