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
1.当我们使用enum 关键字开发一个枚举类时,默认会继承Enum类
2.传统的 public static final Season2 SPRING = new Season2("春天”"温暖"); 简化成 SPRING(“春天”,"温暖”),这里必须知道,它调用的是有参构造器.
3.如果使用无参构造器 创建 枚举对象,则实参列表和小括号都可以省略
4.当有多个枚举对象时,使用,间隔,最后有一个分号结尾
5.枚举对象必须放在枚举类的行首
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 的成员变量
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. 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; } }
反射
是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意属性和方法;这种动态获取信息以及动态调用对象方法的功能称为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); } }
前端工程基于 nginx 运行
启动nginx:双击 nginx.exe 即可启动 nginx 服务,访问端口号为 80
后端工程基于 maven 进行项目构建,并且进行分模块开发
用 IDEA 打开初始工程,了解项目的整体结构
序号 |
名称 |
说明 |
1 |
sky-take-out |
maven父工程,统一管理依赖版本,聚合其他子模块 |
2 |
sky-common |
子模块,存放公共类,例如:工具类、常量类、异常类等 |
3 |
sky-pojo |
子模块,存放实体类、VO、DTO等 |
4 |
sky-server |
子模块,后端服务,存放配置文件、Controller、Service、Mapper等 |
名称 |
说明 |
Entity |
实体,通常和数据库中的表对应 |
DTO |
数据传输对象,通常用于程序中各层之间传递数据 |
VO |
视图对象,为前端展示数据提供的对象 |
POJO |
普通Java对象,只有属性和对应的getter和setter |
sky-common 子模块中存放的是一些公共类,可以供其他模块使用
sky-pojo 子模块中存放的是一些 entity、DTO、VO
sky-server 子模块中存放的是 配置文件、配置类、拦截器、controller、service、mapper、启动类等
使用Git进行项目代码的版本控制,具体操作:
• 创建 Git 本地仓库• 创建 Git 远程仓库• 将本地文件推送到 Git 远程仓库
前端发送的请求,是如何请求到后端服务的?
前端请求地址:http://localhost/api/employee/login
后端接口地址:http://localhost:8080/admin/employee/login
答: 利用nginx反向代理进行
nginx 反向代理,就是将前端发送的动态请求由 nginx 转发到后端服务器
nginx 反向代理的好处:
• 提高访问速度• 进行负载均衡• 保证后端服务安全
所谓负载均衡,就是把大量的请求按照我们指定的方式均衡的分配给集群中的每台服务器
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. 将密码加密后存储,提高安全性2. 使用 MD5 加密方式对明文密码加密
password=DigestUtils.md5DigestAsHex(password.getBytes());
前后端分离开发流程:
介绍:
使用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
用在类上,例如entity、DTO、VO
@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;
}
页面原型
接口设计:
本项目约定:
数据库设计(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 |
根据新增员工接口设计对应的DTO:
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);
新增员工代码出现的问题:
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后,如何传递给Service的save方法?通过线程变量的方法
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());
根据分页查询接口设计对应的DTO:
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>
根据接口定义创建分页查询方法:
/*
* 员工分页查询
* */
@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:
可以通过接口文档进行测试,也可以进行前后端联调测试,最后操作时间字段展示有问题,如下:
解决方式:
/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime createTime;
/*
* 扩展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);
}
}
业务规则:
根据接口设计中的请求参数形式对应的在 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}
编辑员工功能涉及到两个接口:
在 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);
}
业务表中的公共字段:
序号 |
字段名 |
含义 |
数据类型 |
1 |
create_time |
创建时间 |
datetime |
2 |
create_user |
创建人id |
bigint |
3 |
update_time |
修改时间 |
datetime |
4 |
update_user |
修改人id |
bigint |
问题:代码冗余、不便于后期维护
自定义注解 AutoFill ,用于标识需要进行公共字段自动填充的方法自定义切面类 AutoFillAspect ,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值在 Mapper 的方法上加入 AutoFill 注解技术点:枚举、注解、AOP、反射
自定义注解 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
}
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);
}
}
}
}
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);
}
}
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})
业务规则:
根据菜品分页查询接口定义设计对应的DTO:
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;
}
根据接口定义创建DishController的page分页查询方法:
/**
* @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:
业务规则:
数据库设计:
根据删除菜品的接口定义在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}
接口设计:
/**
* @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);
/**
* @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}