苍穹外卖(二)

查漏补缺

1 全局异常处理器Handler

package com.sky.handler;


import com.sky.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;


/*
* 全局异常处理
* 
* */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler2 {


    @ExceptionHandler
    public Result ex(Exception ex){
        log.info("异常信息:{}",ex.getMessage());
        return Result.error(ex.getMessage());
    }
    

}

注解: 

@RestControllerAdvice = @ControllerAdvice + @ResponseBody

@ExceptionHandler

2. 枚举类型

1.当我们使用enum 关键字开发一个枚举类时,默认会继承Enum类

2.传统的 public static final Season2 SPRING = new Season2("春天”"温暖"); 简化成 SPRING(“春天”,"温暖”),这里必须知道,它调用的是有参构造器.

3.如果使用无参构造器 创建 枚举对象,则实参列表和小括号都可以省略

4.当有多个枚举对象时,使用,间隔,最后有一个分号结尾

5.枚举对象必须放在枚举类的行首

 3.注解

1)注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包类、方法、属性、构造器、局部变量等数据信息
2)和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。

常见注解的形式:

public @interface Autowired {
    
}

元注解的基本介绍
JDK 的元 Annotation 用于修饰其他 Annotation
元注解的种类
1) Retention //指定注解的作用范围,三种 SOURCE(源码),CLASS(类),RUNTIME(运行时)

2) Target // 指定注解可以在哪些地方使用

3)Documented //指定该注解是否会在javadoc体现

4)Inherited //子类会继承父类注解
 

@Retention 注解
说明

只能用于修饰一个 Annotation 定义,用于指定该 Annotation 可以保留多长时间,@Rentention 包含一个 RetentiorPolicy 类型的成员变量,使用 @Rentention时必须为该 value 成员变量指定值:
@Retention的三种值

RetentionPolicy.SOURCE: 编译器使用后,直接丢弃这种策略的注释702) RetentionPolicy.CLASS: 编译器将把注解记录在 dass 文件中当运行 Java 程序时,JVM 不会保留注解。 这是默认值

RetentionPolicy.RUNTIME:编译器将把注释记录在 class 文件中当运行中Java 程序时,JVM 会保留注释.程序可以通过反射获取该注解

@Target
基本说明

用于修饰 Annotation 定义,用于指定被修饰的 Annotation 能用于修饰哪些程序元素.@Target 也包含一个名为 value 的成员变量

4. AOP

AOP A spect O riented P rogramming 面向切面编程、面向方面编程 ),其实就是面向特定方法编程。
场景:
u 案例部分功能运行较慢,定位执行耗时较长的业务方法,此时需要统计每一个业务
方法的执行耗时
实现:
u 动态代理是面向切面编程最主流的实现。而 SpringAOP Spring 框架的高级技术,旨在管理 bean 对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。

步骤

l 导入依赖:在 pom.xml 中导入 AOP 的依赖

            org.springframework.boot
            spring-boot-starter-aop
        
l 编写 AOP 程序:针对于特定方法根据业务需要进行编程

package com.itheima.aop;/*
 *
 *  @author pengjx
 *
 * */

import com.github.pagehelper.Page;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Aspect
public class TimeAspect {


    @Around("execution(* com.itheima.service.*.*(..))")//切入点表达式
    public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long begin = System.currentTimeMillis();

        Object proceed = joinPoint.proceed();


        long end = System.currentTimeMillis();

        log.info(joinPoint.getSignature()+"方法执行耗时:{}",end-begin);

        return proceed;

    }



}

 AOP核心概念

l 连接点: JoinPoint ,可以被 AOP 控制的方法(暗含方法执行时的相关信息)
l 通知: Advice ,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
l 切入点: PointCut ,匹配连接点的条件,通知仅会在切入点方法执行时被应用
l 切面: Aspect ,描述通知与切入点的对应关系(通知 + 切入点)
l 目标对象: Target ,通知所应用的对象
 

当多个切面类所包含的切入点表达式相同时可以抽取成为一个方法

苍穹外卖(二)_第1张图片 @PointCut

该注解的作用是将公共 的切点表达式抽取出来 ,需要用到时引用该切点表达式即可。
切入点表达式
切入点表达式:描述切入点方法的一种表达式
作用:主要用来决定项目中的哪些方法需要加入通知
常见形式:
1. execution(……) :根据方法的签名来匹配
2. @annotation(……) :根据注解匹配

切入点表达式-execution

execution 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

l 其中带 ? 的表示可以省略的部分
u 访问修饰符:可省略(比如 : public protected
u 包名 . 类名: 可省略
u throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

l 可以使用通配符描述切入点
* :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分

execution(* com.*.service.*.update*(*))

.. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数

                execution(* com.itheima..DeptService.*(..))

根据业务需要,可以使用 且( && )、或( || )、非( ! ) 来组合比较复杂的切入点表达式。

切入点表达式-@annotation

@annotation 切入点表达式,用于匹配标识有特定注解的方法。 

@annotation(com.itheima.anno.Log)

连接点

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

对于 @Around 通知,获取连接点信息只能使用  ProceedingJoinPoint

对于其他四种通知,获取连接点信息只能使用 JoinPoint ,它是 ProceedingJoinPoint 的父类型

package com.itheima.aop;/*
 *
 *  @author pengjx
 *
 * */


import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Aspect
public class Aspect1 {


    @Pointcut("")
    public void pt(){}


    @Around("pt()")
    public Object aspect(ProceedingJoinPoint joinPoint) throws Throwable {

        //获取目标类名
        String name = joinPoint.getTarget().getClass().getName();

        //获取目标方法名
        Signature signature = joinPoint.getSignature();
        //获取参数
        Object[] args = joinPoint.getArgs();

        //获取目标方法的返回值
        Object proceed = joinPoint.proceed();

        return proceed;

    }
}

5.反射

 反射

是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意属性和方法;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

获取字节码文件对象的三种方式

* Class这个类里面的静态方法forName(“全类名”)**(最常用)**

* 通过class属性获取  

* 通过对象获取字节码文件对象

//1.Class这个类里面的静态方法forName
//Class.forName("类的全类名"): 全类名 = 包名 + 类名
Class clazz1 = Class.forName("com.itheima.reflectdemo.Student");
//源代码阶段获取 --- 先把Student加载到内存中,再获取字节码文件的对象
//clazz 就表示Student这个类的字节码文件对象。
//就是当Student.class这个文件加载到内存之后,产生的字节码文件对象


//2.通过class属性获取
//类名.class
Class clazz2 = Student.class;

//因为class文件在硬盘中是唯一的,所以,当这个文件加载到内存之后产生的对象也是唯一的
System.out.println(clazz1 == clazz2);//true


//3.通过Student对象获取字节码文件对象
Student s = new Student();
Class clazz3 = s.getClass();
System.out.println(clazz1 == clazz2);//true
System.out.println(clazz2 == clazz3);//true

获取构造方法

规则:

​   get表示获取

​   Declared表示私有

​   最后的s表示所有,复数形式

​   如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用

| 方法名                                                       | 说明                              |
| ------------------------------------------------------------ | --------------------------------- |
| Constructor[] getConstructors()                           | 获得所有的构造(只能public修饰)  |
| Constructor[] getDeclaredConstructors()                   | 获得所有的构造(包含private修饰) |
| Constructor getConstructor(Class... parameterTypes)    | 获取指定构造(只能public修饰)    |
| Constructor getDeclaredConstructor(Class... parameterTypes) | 获取指定构造(包含private修饰)   |
public class ReflectDemo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //1.获得整体(class字节码文件对象)
        Class clazz = Class.forName("com.itheima.reflectdemo.Student");


        //2.获取构造方法对象
        //获取所有构造方法(public)
        Constructor[] constructors1 = clazz.getConstructors();
        for (Constructor constructor : constructors1) {
            System.out.println(constructor);
        }

        System.out.println("=======================");

        //获取所有构造(带私有的)
        Constructor[] constructors2 = clazz.getDeclaredConstructors();

        for (Constructor constructor : constructors2) {
            System.out.println(constructor);
        }
        System.out.println("=======================");

        //获取指定的空参构造
        Constructor con1 = clazz.getConstructor();
        System.out.println(con1);

        Constructor con2 = clazz.getConstructor(String.class,int.class);
        System.out.println(con2);

        System.out.println("=======================");
        //获取指定的构造(所有构造都可以获取到,包括public包括private)
        Constructor con3 = clazz.getDeclaredConstructor();
        System.out.println(con3);
        //了解 System.out.println(con3 == con1);
        //每一次获取构造方法对象的时候,都会新new一个。

        Constructor con4 = clazz.getDeclaredConstructor(String.class);
        System.out.println(con4);
    }
}
//需求1:
//获取空参,并创建对象

//1.获取整体的字节码文件对象
Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
//2.获取空参的构造方法
Constructor con = clazz.getConstructor();
//3.利用空参构造方法创建对象
Student stu = (Student) con.newInstance();
System.out.println(stu);


System.out.println("=============================================");


//测试类中的代码:
//需求2:
//获取带参构造,并创建对象
//1.获取整体的字节码文件对象
Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
//2.获取有参构造方法
Constructor con = clazz.getDeclaredConstructor(String.class, int.class);
//3.临时修改构造方法的访问权限(暴力反射)
con.setAccessible(true);
//4.直接创建对象
Student stu = (Student) con.newInstance("zhangsan", 23);
System.out.println(stu);

获取成员变量

规则:

​   get表示获取

​   Declared表示私有

​   最后的s表示所有,复数形式

​   如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用

| 方法名                              | 说明                                         |
| ----------------------------------- | -------------------------------------------- |
| Field[] getFields()                 | 返回所有成员变量对象的数组(只能拿public的) |
| Field[] getDeclaredFields()         | 返回所有成员变量对象的数组,存在就能拿到     |
| Field getField(String name)         | 返回单个成员变量对象(只能拿public的)       |
| Field getDeclaredField(String name) | 返回单个成员变量对象,存在就能拿到           |
public class ReflectDemo4 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //获取成员变量对象

        //1.获取class对象
        Class clazz = Class.forName("com.itheima.reflectdemo.Student");

        //2.获取成员变量的对象(Field对象)只能获取public修饰的
        Field[] fields1 = clazz.getFields();
        for (Field field : fields1) {
            System.out.println(field);
        }

        System.out.println("===============================");

        //获取成员变量的对象(public + private)
        Field[] fields2 = clazz.getDeclaredFields();
        for (Field field : fields2) {
            System.out.println(field);
        }

        System.out.println("===============================");
        //获得单个成员变量对象
        //如果获取的属性是不存在的,那么会报异常
        //Field field3 = clazz.getField("aaa");
        //System.out.println(field3);//NoSuchFieldException

        Field field4 = clazz.getField("gender");
        System.out.println(field4);

        System.out.println("===============================");
        //获取单个成员变量(私有)
        Field field5 = clazz.getDeclaredField("name");
        System.out.println(field5);

    }
}

 获取成员变量并获取值和修改值

| 方法                                | 说明   |
| ----------------------------------- | ------ |
| void set(Object obj, Object value) | 赋值   |
| Object get(Object obj)              | 获取值 |

public class ReflectDemo5 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Student s = new Student("zhangsan",23,"广州");
        Student ss = new Student("lisi",24,"北京");

        //需求:
        //利用反射获取成员变量并获取值和修改值

        //1.获取class对象
        Class clazz = Class.forName("com.itheima.reflectdemo.Student");

        //2.获取name成员变量
        //field就表示name这个属性的对象
        Field field = clazz.getDeclaredField("name");
        //临时修饰他的访问权限
        field.setAccessible(true);

        //3.设置(修改)name的值
        //参数一:表示要修改哪个对象的name?
        //参数二:表示要修改为多少?
        field.set(s,"wangwu");

        //3.获取name的值
        //表示我要获取这个对象的name的值
        String result = (String)field.get(s);

        //4.打印结果
        System.out.println(result);

        System.out.println(s);
        System.out.println(ss);

    }
}

 获取成员方法

规则:

​   get表示获取

​   Declared表示私有

​   最后的s表示所有,复数形式

​   如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用

| 方法名                                                       | 说明                                         |
| ------------------------------------------------------------ | -------------------------------------------- |
| Method[] getMethods()                                        | 返回所有成员方法对象的数组(只能拿public的) |
| Method[] getDeclaredMethods()                                | 返回所有成员方法对象的数组,存在就能拿到     |
| Method getMethod(String name, Class... parameterTypes)    | 返回单个成员方法对象(只能拿public的)       |
| Method getDeclaredMethod(String name, Class... parameterTypes) | 返回单个成员方法对象,存在就能拿到           |
public class ReflectDemo6 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //1.获取class对象
        Class clazz = Class.forName("com.itheima.reflectdemo.Student");


        //2.获取方法
        //getMethods可以获取父类中public修饰的方法
        Method[] methods1 = clazz.getMethods();
        for (Method method : methods1) {
            System.out.println(method);
        }

        System.out.println("===========================");
        //获取所有的方法(包含私有)
        //但是只能获取自己类中的方法
        Method[] methods2 = clazz.getDeclaredMethods();
        for (Method method : methods2) {
            System.out.println(method);
        }

        System.out.println("===========================");
        //获取指定的方法(空参)
        Method method3 = clazz.getMethod("sleep");
        System.out.println(method3);

        Method method4 = clazz.getMethod("eat",String.class);
        System.out.println(method4);

        //获取指定的私有方法
        Method method5 = clazz.getDeclaredMethod("playGame");
        System.out.println(method5);
    }
}

获取成员方法并运行

方法

 Object invoke(Object obj, Object... args) :运行方法

参数一:用obj对象调用该方法

参数二:调用方法的传递的参数(如果没有就不写)

返回值:方法的返回值(如果没有就不写)

package com.itheima.a02reflectdemo1;

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

public class ReflectDemo6 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //1.获取字节码文件对象
        Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
		
        //2.获取一个对象
        //需要用这个对象去调用方法
        Student s = new Student();
        
        //3.获取一个指定的方法
        //参数一:方法名
        //参数二:参数列表,如果没有可以不写
        Method eatMethod = clazz.getMethod("eat",String.class);
        
        //运行
        //参数一:表示方法的调用对象
        //参数二:方法在运行时需要的实际参数
        //注意点:如果方法有返回值,那么需要接收invoke的结果
        //如果方法没有返回值,则不需要接收
        String result = (String) eatMethod.invoke(s, "重庆小面");
        System.out.println(result);

    }
}

1. 开发环境搭建

1.1 整体结构

苍穹外卖(二)_第2张图片

1.2 前端环境搭建

前端工程基于 nginx 运行

苍穹外卖(二)_第3张图片

启动nginx:双击 nginx.exe 即可启动 nginx 服务,访问端口号为 80

1.3 后端环境搭建

1.3.1 熟悉项目结构

后端工程基于 maven 进行项目构建,并且进行分模块开发

苍穹外卖(二)_第4张图片

IDEA 打开初始工程,了解项目的整体结构

序号

名称

说明

1

sky-take-out

maven父工程,统一管理依赖版本,聚合其他子模块

2

sky-common

子模块,存放公共类,例如:工具类、常量类、异常类等

3

sky-pojo

子模块,存放实体类、VODTO

4

sky-server

子模块,后端服务,存放配置文件、ControllerServiceMapper

名称

说明

Entity

实体,通常和数据库中的表对应

DTO

数据传输对象,通常用于程序中各层之间传递数据

VO

视图对象,为前端展示数据提供的对象

POJO

普通Java对象,只有属性和对应的gettersetter

sky-common 子模块中存放的是一些公共类,可以供其他模块使用

sky-pojo 子模块中存放的是一些 entityDTOVO

sky-server 子模块中存放的是 配置文件、配置类、拦截器、controllerservicemapper、启动类等

1.3.2 使用Git进行版本控制

使用Git进行项目代码的版本控制,具体操作:

创建 Git 本地仓库
创建 Git 远程仓库
将本地文件推送到 Git 远程仓库
1.3.3 前后端联调

前端发送的请求,是如何请求到后端服务的?

前端请求地址:http://localhost/api/employee/login

后端接口地址:http://localhost:8080/admin/employee/login

答: 利用nginx反向代理进行

nginx 反向代理,就是将前端发送的动态请求由 nginx 转发到后端服务器

苍穹外卖(二)_第5张图片

nginx 反向代理的好处:

提高访问速度
进行负载均衡
保证后端服务安全

苍穹外卖(二)_第6张图片

所谓负载均衡,就是把大量的请求按照我们指定的方式均衡的分配给集群中的每台服务器

nginx 反向代理的配置方式:

server{
	listen 80;
	server_name localhost;
	
	location /api/ {
            		proxy_pass   http://localhost:8080/admin/;  #反向代理
	}

}

nginx 负载均衡的配置方式:

upstream webservers{
	server 192.168.100.128:8080;
	server 192.168.100.129:8080;
}

server{
	listen 80;
	server_name localhost;
	
	location /api/ {
            		proxy_pass   http://webservers/admin/;  #负载均衡
	}

}

1.4 完善登录功能

问题:员工表中的密码是明文存储,安全性太低。

思路:

1. 将密码加密后存储,提高安全性
2. 使用 MD5 加密方式对明文密码加密
1. 修改数据库中明文密码,改为 MD5 加密后的密文
2. 修改 Java 代码,前端提交的密码进行 MD5 加密后再跟数据库中密码比对
 password=DigestUtils.md5DigestAsHex(password.getBytes());

2. 导入接口文档

前后端分离开发流程:

苍穹外卖(二)_第7张图片

3.Swagger

介绍:

使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。

官网:https://swagger.io/

Knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。

使用方法

1. 导入 knife4j maven 坐标

            com.github.xiaoymin
            knife4j-spring-boot-starter
            3.0.2

2. 在配置类中加入 knife4j 相关配置
/**
     * 通过knife4j生成接口文档
     * @return
     */
    @Bean
    public Docket docket() {
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }
3. 设置静态资源映射,否则接口文档页面无法访问
/**
     * 设置静态资源映射
     * @param registry
     */
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

通过 Swagger 就可以生成接口文档,那么我们就不需要 Yapi 了?

1、Yapi 是设计阶段使用的工具,管理和维护接口

2、Swagger 在开发阶段使用的框架,帮助后端开发人员做后端的接口测试

常用注解

通过注解可以控制生成的接口文档,使接口文档拥有更好的可读性,常用注解如下:

注解

说明

@Api

用在类上,例如Controller,表示对类的说明

@ApiModel

用在类上,例如entityDTOVO

@ApiModelProperty

用在属性上,描述属性信息

@ApiOperation

用在方法上,例如Controller的方法,说明方法的用途、作用

@Api(tags = "员工相关接口")
public class EmployeeController {

}
 @PostMapping("/login")
    @ApiOperation(value = "员工登录")
    public Result login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
}
@Data
@ApiModel(description = "员工登录时传递的数据模型")
public class EmployeeLoginDTO implements Serializable {

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("密码")
    private String password;

}

4.新增员工

4.1 需求分析与设计

页面原型

苍穹外卖(二)_第8张图片

接口设计:

苍穹外卖(二)_第9张图片

苍穹外卖(二)_第10张图片

本项目约定:

管理端 发出的请求,统一使用 /admin 作为前缀
用户端 发出的请求,统一使用 /user 作为前缀

数据库设计(employee表):

字段名

数据类型

说明

备注

id

bigint

主键

自增

name

varchar(32)

姓名

username

varchar(32)

用户名

唯一

password

varchar(64)

密码

phone

varchar(11)

手机号

sex

varchar(2)

性别

id_number

varchar(18)

身份证号

status

Int

账号状态

1正常 0锁定

create_time

Datetime

创建时间

update_time

datetime

最后修改时间

create_user

bigint

创建人id

update_user

bigint

最后修改人id

4.2 代码开发

根据新增员工接口设计对应的DTO

苍穹外卖(二)_第11张图片

package com.sky.dto;

import lombok.Data;

import java.io.Serializable;

@Data
public class EmployeeDTO implements Serializable {

    private Long id;

    private String username;

    private String name;

    private String phone;

    private String sex;

    private String idNumber;

}

注意:当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据

EmployeeController中创建新增员工方法,接收前端提交的参数:

@PostMapping
    @ApiOperation("新增员工")
    public Result save(@RequestBody EmployeeDTO employeeDTO){
        log.info("性质员工:{}",employeeDTO);
        employeeService.save(employeeDTO);
        return Result.success();
    }

EmployeeService接口中声明新增员工方法:


    /*
     * 新增员工
     * */
    void save(EmployeeDTO employeeDTO);

EmployeeServiceImpl中实现新增员工方法:

/*
    * 新增员工
    * */
    @Override
    public void save(EmployeeDTO employeeDTO) {
        Employee employee=new Employee();
        //将employee的熟悉赋值给employee
        BeanUtils.copyProperties(employeeDTO,employee);

        //设置新增员工的默认状态为1
        employee.setStatus(StatusConstant.ENABLE);

        //新员工的默认密码为md5加密的123456
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));

        //设置创建时间和修改时间
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());

        //创建修改人的id
        //TODO 后期需要修改为修改人的id
        employee.setCreateUser(10L);
        employee.setUpdateUser(10L);

        //调用持久层
        employeeMapper.insert(employee);




    }

EmployeeMapper中声明insert方法:

 /*
    * 新增员工
    * */
    @Insert("insert into 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.3 代码完善

新增员工代码出现的问题:

1. 输入重复的用户名,会出现报.SQLIntegrityConstraintViolationException异常,录入的用户名已存在,抛出异常后没有处理

解决方案:使用全局异常处理器

/*
    * 捕获用户名重复异常
    * */
    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){

        String message = ex.getMessage();
        if (message.contains("Duplicate entry")) {
            String[] split = message.split(" ");
            String username=split[2];

            return Result.error(username+ MessageConstant.ALREADY_EXISTS);

        }

        return Result.error(MessageConstant.UNKNOWN_ERROR);


    }

2.•新增员工时,创建人id和修改人id设置为了固定值

针对第二个问题,需要通过某种方式动态获取当前登录员工的id,员工登录成功后会生成JWT令牌并响应给前端,后续请求中,前端会携带JWT令牌,通过JWT令牌可以解析出当前登录员工id。

Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:", empId);

解析出登录员工id后,如何传递给Servicesave方法?通过线程变量的方法

ThreadLocal 并不是一个Thread,而是Thread的局部变量。

ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。

ThreadLocal常用方法:

public void set( T value)   设置当前线程的线程局部变量的值
public T get()   返回当前线程所对应的线程局部变量的值
public void remove()        移除当前线程的线程局部变量
注意 :客户端发送的每次请求,后端的 Tomcat 服务器都会分配一个单独的线程来处理请求

初始工程中已经封装了 ThreadLocal 操作的工具类:

package com.sky.context;

public class BaseContext {

    public static ThreadLocal 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();
    }

}

在拦截器中解析出当前登录员工id,并放入线程局部变量中:

BaseContext.setCurrentId(empId);

Service中获取线程局部变量中的值:

employee.setCreateUser(BaseContext.getCurrentId());
        employee.setUpdateUser(BaseContext.getCurrentId());

5.员工分页查询

5.1 需求分析和设计

根据分页查询接口设计对应的DTO

苍穹外卖(二)_第12张图片

package com.sky.dto;

import lombok.Data;

import java.io.Serializable;

@Data
public class EmployeePageQueryDTO implements Serializable {

    //员工姓名
    private String name;

    //页码
    private int page;

    //每页显示记录数
    private int pageSize;

}

后面所有的分页查询,统一都封装成PageResult对象:

package com.sky.result;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

/**
 * 封装分页查询结果
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {

    private long total; //总记录数

    private List records; //当前页数据集合

}

员工信息分页查询后端返回的对象类型为:Result<PageResult>

苍穹外卖(二)_第13张图片

5.2 代码开发

根据接口定义创建分页查询方法:

/*
    * 员工分页查询
    * */
    @GetMapping("/page")
    @ApiOperation("分页查询")
    public Result page(EmployeePageQueryDTO employeePageQueryDTO){
        log.info("员工分页查询:{}",employeePageQueryDTO);
        PageResult page=employeeService.pageQuery(employeePageQueryDTO);
        return Result.success(page);
    }

EmployeeService接口中声明pageQuery方法:

 PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

EmployeeServiceImpl 中实现 pageQuery 方法:

/*
    * 员工分页查询
    * */
    @Override
    public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {


        //分页查询
        PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());


        Page page=employeeMapper.pageQuery(employeePageQueryDTO);

        PageResult pageResult=new PageResult();

        pageResult.setTotal(page.getTotal());
        pageResult.setRecords(page.getResult());
        return pageResult;
    }

注意:此处使用 mybatis 的分页插件 PageHelper 来简化分页代码的开发。底层基于 mybatis 的拦截器实现。


                com.github.pagehelper
                pagehelper-spring-boot-starter
                ${pagehelper}
            

EmployeeMapper 中声明 pageQuery 方法:

Page pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

EmployeeMapper.xml 中编写SQL




    

5.3 代码完善

可以通过接口文档进行测试,也可以进行前后端联调测试,最后操作时间字段展示有问题,如下:

苍穹外卖(二)_第14张图片

解决方式:

方式一:在属性上加入注解,对日期进行格式化
/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
    private LocalDateTime createTime;
方式二:在 WebMvcConfiguration 中扩展 Spring MVC 的消息转换器,统一对日期类型进行格式化处理
/*
    * 扩展SpringMVC框架的消息转换器
    * 统一对前端传送给后端进行转换处理
    * */
    @Override
    protected void extendMessageConverters(List> converters) {

        log.info("拓展消息转换器");
        //创建一个消息转换器
        MappingJackson2HttpMessageConverter converter=new MappingJackson2HttpMessageConverter();
        //需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为JSON数据
        converter.setObjectMapper(new JacksonObjectMapper());
        //将自己的消息转化器加入到容器中
        converters.add(0,converter);
    }
package com.sky.json;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    //public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

6.启用禁用员工账号

6.1 需求分析和设计

业务规则:

可以对状态为“启用” 的员工账号进行“禁用”操作
可以对状态为“禁用”的员工账号进行“启用”操作
状态为“禁用”的员工账号不能登录系统

苍穹外卖(二)_第15张图片

6.2 代码开发

根据接口设计中的请求参数形式对应的在 EmployeeController 中创建启用禁用员工账号的方法:

   /*
    *根据id修改状态
    * */

    @PostMapping("/status/{status}")
    public Result startOrStop(@PathVariable Integer status,Long id){
        log.info("员工状态和id:{},{}",status,id);
        employeeService.startOrStop(status,id);
        return Result.success();
    }

EmployeeService 接口中声明启用禁用员工账号的业务方法:

*
     * 根据id修改员工状态
     * */
    void startOrStop(Integer status, Long id);

EmployeeServiceImpl 中实现启用禁用员工账号的业务方法: 

/*
    * 根据id修改员工状态
    * */
    @Override
    public void startOrStop(Integer status, Long id) {
        Employee employee = Employee.builder()
                .status(status)
                .id(id)
                .build();
        employeeMapper.update(employee);
    }

EmployeeMapper 接口中声明 update 方法:

void update(Employee employee);

EmployeeMapper.xml 中编写SQL



            update employee
            

                username = #{username},
                name = #{name},
                password = #{password},
                phone = #{phone},
                sex = #{sex},
                id_Number = #{idNumber},
                update_Time = #{updateTime},
                update_User = #{updateUser},
                status = #{status},

            

            
                id = #{id}
            

    

7 编辑员工

7.1 需求分析和设计

编辑员工功能涉及到两个接口:

根据 id 查询员工信息
苍穹外卖(二)_第16张图片
编辑员工信息

苍穹外卖(二)_第17张图片

7.2 代码开发

EmployeeController 中创建 getById 方法:

/*
    * 根据id查询员工信息
    * */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询员工")
    public Result getById(@PathVariable Long id){
        log.info("根据id查询员工信息:{}",id);
        Employee employee=employeeService.getById(id);
        return Result.success(employee);
    }

EmployeeService 接口中声明 getById 方法:

/*
    * 根据id查询用户
    * */
    Employee getById(Long id);

EmployeeServiceImpl 中实现 getById 方法:

/*
    * 根据id查询用户
    * */
    @Override
    public Employee getById(Long id) {
        Employee employee=employeeMapper.getByid(id);

        return employee;
    }

EmployeeMapper 接口中声明 getById 方法:

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

EmployeeController 中创建 update 方法:

/*
    * 编辑员工
    * */
    @PutMapping
    @ApiOperation("编辑员工")
    public Result update(@RequestBody EmployeeDTO employeeDTO){
        log.info("编辑员工:{}",employeeDTO);
        employeeService.update(employeeDTO);
        return Result.success();
    }

EmployeeService 接口中声明 update 方法:

/*
    * 编辑员工
    * */
    void update(EmployeeDTO employeeDTO);

EmployeeServiceImpl 中实现 update 方法:

/*
    * 编辑员工
    * */
    @Override
    public void update(EmployeeDTO employeeDTO) {

        Employee employee=new Employee();
        BeanUtils.copyProperties(employeeDTO,employee);
        employee.setUpdateTime(LocalDateTime.now());
        employee.setUpdateUser(BaseContext.getCurrentId());

        employeeMapper.update(employee);

    }

8. 公共字段自动填充

8.1 问题分析

业务表中的公共字段:

序号

字段名

含义

数据类型

1

create_time

创建时间

datetime

2

create_user

创建人id

bigint

3

update_time

修改时间

datetime

4

update_user

修改人id

bigint

问题:代码冗余、不便于后期维护

8.2 实现思路

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

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

8.3 代码开发

自定义注解 AutoFill

package com.sky.annotation;/*
 *
 *  @author pengjx
 *
 * */

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();
}
自定义注解
package com.sky.enumeration;

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

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

    /**
     * 插入操作
     */
    INSERT

}
自定义切面 AutoFillAspect
package com.sky.aspect;/*
 *
 *  @author pengjx
 *
 * */


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 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.Method;
import java.time.LocalDateTime;

@Component
@Slf4j
@Aspect
public class AutoFillAspect {

    @Pointcut("@annotation(com.sky.annotation.AutoFill)")
    public void pt(){}

    @Before("pt()")
    public void autoFill(JoinPoint joinPoint)  {
        log.info("公共字段自动填充...");
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);

        Object[] args = joinPoint.getArgs();

        if(args==null || args.length==0){
            return;
        }

        Object object = args[0];

        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        if(autoFill.value()==OperationType.INSERT){
            try {
                Method setCreateTime = object.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setUpdateTime = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setCreateUser = object.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateUser = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                setCreateTime.invoke(object,now);
                setUpdateTime.invoke(object,now);
                setCreateUser.invoke(object,currentId);
                setUpdateUser.invoke(object,currentId);


            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }else{
            try {
                Method setUpdateTime = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                setUpdateTime.invoke(object,now);
                setUpdateUser.invoke(object,currentId);


            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }


    }

}

9.新增菜品

9.1 文件上传接口代码开发

application.yml

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

application-dev.yml

sky:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    host: localhost
    port: 3306
    database: sky_take_out
    username: root
    password: Pengjixuan0524.

  alioss:
    bucket-name: web-test-talias
    access-key-id: LTAI5tGJ5rNa4wi7ni92nKmF
    access-key-secret: tTm9N9Vh8qe0XcPCgYUbCXDcOhn4C4
    endpoint: oss-cn-hangzhou.aliyuncs.com

OssConfiguration

package com.sky.config;
/*
 *
 *  @author pengjx
 *
 * */

import com.sky.properties.AliOssProperties;
import com.sky.utils.AliOssUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class OssConfiguration {

    /**
     * @description:
     * @date: 2023/12/15 20:26 
 * @param: aliOssProperties 
 * @return: com.sky.utils.AliOssUtil
     **/
    @Bean
    @ConditionalOnMissingBean
    public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
        log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties);
        return new AliOssUtil(aliOssProperties.getEndpoint(),
                aliOssProperties.getAccessKeyId(),
                aliOssProperties.getAccessKeySecret(),
                aliOssProperties.getBucketName());
    }
}

CommonController

package com.sky.controller.admin;/*
 *
 *  @author pengjx
 *
 * */

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.aspectj.lang.annotation.Pointcut;
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;

    /**
     * @description: 文件上传
     * @date: 2023/12/15 20:47
     * @param: file
     * @return: com.sky.result.Result
     **/
    @PostMapping("/upload")
    @ApiOperation("文件上传")
    public Result upload(MultipartFile file){

        log.info(file.getName());

        String filename = file.getOriginalFilename();

        String substring = filename.substring(filename.lastIndexOf("."));

        String object = UUID.randomUUID().toString() + substring;


        try {
            String upload = aliOssUtil.upload(file.getBytes(), object);
            return Result.success(upload);
        } catch (IOException e) {
            log.info("文件上传失败:{}",e.getMessage());
        }

        return Result.error(MessageConstant.UPLOAD_FAILED);


    }

}

9.2 新增菜品接口

DishController

package com.sky.controller.admin;/*
 *
 *  @author pengjx
 *
 * */


import com.sky.dto.DishDTO;
import com.sky.result.Result;
import com.sky.service.DishService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/admin/dish")
@Api("菜品相关接口")
@Slf4j
public class DishController {

    @Autowired
    private DishService dishService;


    /**
     * @description: 新增菜品
     * @date: 2023/12/16 8:54
     * @param: dishDTO
     * @return: com.sky.result.Result
     **/
    @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO){
        log.info("新增菜品:{}",dishDTO);
        dishService.saveWithFlavor(dishDTO);
        return Result.success();
    }

}

DishService

package com.sky.service;/*
 *
 *  @author pengjx
 *
 * */

import com.sky.dto.DishDTO;
import org.springframework.stereotype.Service;

@Service
public interface DishService {


    /**
     * @description: 新增菜品
     * @date: 2023/12/16 8:57
     * @param: dishDTO
     **/
    public void saveWithFlavor(DishDTO dishDTO);

}

DishServiceImpl

package com.sky.service.impl;/*
 *
 *  @author pengjx
 *
 * */

import com.sky.dto.DishDTO;
import com.sky.entity.Dish;
import com.sky.entity.DishFlavor;
import com.sky.mapper.DishFlavorMapper;
import com.sky.mapper.DishMapper;
import com.sky.service.DishService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class DishServiceImpl implements DishService {

    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private DishFlavorMapper dishFlavorMapper;



    /**
     * @description: 新增菜品
     * @date: 2023/12/16 8:58
     * @param: dishDTO
     **/
    @Override
    @Transactional
    public void saveWithFlavor(DishDTO dishDTO) {

        Dish dish=new Dish();

        BeanUtils.copyProperties(dishDTO,dish);

        dishMapper.insert(dish);

        Long dishId = dish.getId();

        List flavors = dishDTO.getFlavors();

        if(flavors!=null && flavors.size()!=0){
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });

            dishFlavorMapper.insertBatch(flavors);
        }


    }
}
DishMapper
package com.sky.mapper;

import com.sky.annotation.AutoFill;
import com.sky.entity.Dish;
import com.sky.enumeration.OperationType;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.beans.factory.annotation.Autowired;

@Mapper
public interface DishMapper {

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

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

}





    

        insert into dish (status,name, category_id, price, image, description, create_time, update_time, create_user, update_user)
        values
            (#{status}, #{name}, #{categoryId}, #{price}, #{image}, #{description}, #{createTime}, #{updateTime},#{createUser}, #{updateUser})


    
DishFlavorMapper
package com.sky.mapper;/*
 *
 *  @author pengjx
 *
 * */

import com.sky.annotation.AutoFill;
import com.sky.entity.DishFlavor;
import com.sky.enumeration.OperationType;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface DishFlavorMapper {



    void insertBatch(List flavors);
}





    

        insert into dish_flavor (dish_id, name, value)
        values
            
                (#{df.dishId},#{df.name},#{df.value})
            



    

10.菜品分页查询

10.1 需求分析和设计

业务规则:

根据页码展示菜品信息
每页展示 10 条数据
分页查询时可以根据需要输入菜品名称、菜品分类、菜品状态进行查询

苍穹外卖(二)_第18张图片

10.2 代码开发

根据菜品分页查询接口定义设计对应的DTO

苍穹外卖(二)_第19张图片

package com.sky.dto;

import lombok.Data;

import java.io.Serializable;

@Data
public class DishPageQueryDTO implements Serializable {

    private int page;

    private int pageSize;

    private String name;

    //分类id
    private Integer categoryId;

    //状态 0表示禁用 1表示启用
    private Integer status;

}

根据菜品分页查询接口定义设计对应的VO

package com.sky.vo;

import com.sky.entity.DishFlavor;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DishVO implements Serializable {

    private Long id;
    //菜品名称
    private String name;
    //菜品分类id
    private Long categoryId;
    //菜品价格
    private BigDecimal price;
    //图片
    private String image;
    //描述信息
    private String description;
    //0 停售 1 起售
    private Integer status;
    //更新时间
    private LocalDateTime updateTime;
    //分类名称
    private String categoryName;
    //菜品关联的口味
    private List flavors = new ArrayList<>();

    //private Integer copies;
}

根据接口定义创建DishControllerpage分页查询方法:

/**
     * @description: 菜品分页查询
     * @date: 2023/12/16 19:17
     * @param: dishPageQueryDTO
     * @return: com.sky.result.Result
     **/
    @GetMapping("/page")
    @ApiOperation("菜品分页查询")
    public Result page(DishPageQueryDTO dishPageQueryDTO){
        log.info("菜品分页查询:{}",dishPageQueryDTO);
        PageResult pageResult=dishService.pageQuery(dishPageQueryDTO);
        return Result.success(pageResult);
    }

DishService 中扩展分页查询方法:

/**
     * @description: 菜品分页查询
     * @date: 2023/12/16 19:19
     * @param: dishPageQueryDTO
     * @return: com.sky.result.PageResult
     **/
    PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO);

DishServiceImpl 中实现分页查询方法:

/**
     * @description: 菜品分页查询
     * @date: 2023/12/16 19:20
     * @param: dishPageQueryDTO
     * @return: com.sky.result.PageResult
     **/
    @Override
    public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
        PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());

        Page page = dishMapper.pageQuery(dishPageQueryDTO);

        return new PageResult(page.getTotal(),page.getResult());

    }

DishMapper 接口中声明 pageQuery 方法:

Page pageQuery(DishPageQueryDTO dishPageQueryDTO);

DishMapper.xml 中编写SQL

11.批量删除菜品

11.1 需求分析和设计

业务规则:

可以一次删除一个菜品,也可以批量删除菜品
起售中的菜品不能删除
被套餐关联的菜品不能删除
删除菜品后,关联的口味数据也需要删除掉

苍穹外卖(二)_第20张图片

数据库设计:

苍穹外卖(二)_第21张图片

11.2 代码开发

根据删除菜品的接口定义在DishController中创建方法:

/**
     * @description: 批量删除菜品
     * @date: 2023/12/16 20:12
     * @param: ids
     * @return: com.sky.result.Result
     **/
    @DeleteMapping
    @ApiOperation("批量删除菜品")
    public Result delete(@RequestParam List ids){
        log.info("批量删除菜品:{}",ids);
        dishService.deleteBatch(ids);
        return Result.success();
    }

DishService接口中声明deleteBatch方法:

/**
     * @description: 批量删除菜品
     * @date: 2023/12/16 20:12
     * @param: ids
     **/
    void deleteBatch(List ids);

DishServiceImpl中实现deleteBatch方法:

/**
     * @description: 批量删除菜品
     * @date: 2023/12/16 20:13
     * @param: ids
     **/
    @Override
    @Transactional
    public void deleteBatch(List ids) {
        for (Long id : ids) {
            Dish dish=dishMapper.getById(id);
            if(dish.getStatus()== StatusConstant.ENABLE){
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        }

        List setmealIds=setmealDishMapper.getSetmealIdsByDishIds(ids);
        if(setmealIds !=null && setmealIds.size()>0){
            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
        }

        dishMapper.deleteByIds(ids);

        dishFlavorMapper.deleteByIds(ids);




    }

DishMapper中声明getById方法,并配置SQL

 @Select("select * from dish where id= #{id}")
    Dish getById(Long id);

创建SetmealDishMapper,声明getSetmealIdsByDishIds方法,并在xml文件中编写SQL

 List getSetmealIdsByDishIds(List ids);




    

DishMapper.xml中声明deleteById方法并配置SQL


            delete from dish where id in
                             
                                 #{id}
                             

    

DishFlavorMapper中声明deleteByDishId方法并配置SQL


        delete from dish_flavor where dish_flavor.dish_id in
        
            #{dishId}
        
    

12.修改菜品

12.1 需求分析和设计

接口设计:

根据 id 查询菜品
苍穹外卖(二)_第22张图片
根据类型查询分类(已实现)
文件上传(已实现)
修改菜品
苍穹外卖(二)_第23张图片

12.2 根据id查询菜品代码开发

/**
     * @description: 根据id查询菜品
     * @date: 2023/12/17 8:48
     * @param: id
     * @return: com.sky.result.Result
     **/
    @GetMapping("/{id}")
    @ApiOperation("根据id查询菜品")
    public Result getById(@PathVariable Long id){
        log.info("根据id查询菜品:{}",id);
        DishVO dishVO=dishService.getByIdWithFlavor(id);
        return Result.success(dishVO);

    }
/**
     * @description: 根据id查询菜品
     * @date: 2023/12/17 8:50
     * @param: id
     * @return: com.sky.vo.DishVO
     **/
    DishVO getByIdWithFlavor(Long id);
/**
     * @description: 根据id查询菜品
     * @date: 2023/12/17 8:50
     * @param: id
     * @return: com.sky.vo.DishVO
     **/
    @Override
    public DishVO getByIdWithFlavor(Long id) {
        Dish dish = dishMapper.getById(id);

        List dishFlavors=dishFlavorMapper.getByDishId(id);

        DishVO dishVO=new DishVO();

        BeanUtils.copyProperties(dish,dishVO);
        dishVO.setFlavors(dishFlavors);

        return dishVO;
    }
@Select("select * from dish_flavor where dish_id=#{dishId}")
    List getByDishId(Long dishId);

12.3 修改菜品代码开发:

/**
     * @description: 修改菜品
     * @date: 2023/12/17 9:05
     * @param: dishDTO
     * @return: com.sky.result.Result
     **/
    @PutMapping
    @ApiOperation("修改菜品")
    public Result update(@RequestBody DishDTO dishDTO){

        log.info("修改菜品:{}",dishDTO);
        dishService.update(dishDTO);
        return Result.success();

    }
/**
     * @description: 修改菜品
     * @date: 2023/12/17 9:05
     * @param: dishDTO
     **/
    void update(DishDTO dishDTO);
/**
     * @description: 修改菜品
     * @date: 2023/12/17 9:05
     * @param: dishDTO
     **/
    @Override
    public void update(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO,dish);
        dishMapper.update(dish);

        dishFlavorMapper.deleteByDishId(dishDTO.getId());

        List flavors = dishDTO.getFlavors();
        if(flavors!=null && flavors.size()!=0){
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishDTO.getId());
            });

            dishFlavorMapper.insertBatch(flavors);
        }

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

        update dish
        
            
                name = #{name},
            
            
                category_id = #{categoryId},
            
            
                price = #{price},
            
            
                image = #{image},
            
            
                description = #{description},
            
            
                status = #{status},
            
            
                update_time = #{updateTime},
            
            
                update_user = #{updateUser},
            

        
        where id=#{id}
    

你可能感兴趣的:(苍穹外卖,java,开发语言,spring,boot,后端)