Commit on 2025/04/16 周三 18:02:24.06

This commit is contained in:
zhangsan 2025-04-16 18:02:24 +08:00
parent e9e0a05fe4
commit 09893030df
3 changed files with 507 additions and 352 deletions

View File

@ -178,7 +178,7 @@ Maven 重建
什么是坐标?
* Maven中的坐标是==资源的唯一标识== , 通过该坐标可以唯一定位资源位置
* Maven中的坐标是 == 资源的唯一标识 == 通过该坐标可以唯一定位资源位置
* 使用坐标来定义项目或引入项目中需要的依赖
![image-20240302131843540](https://pic.bitday.top/i/2025/03/19/u6ps37-2.png)
@ -319,6 +319,8 @@ A依赖BB依赖C如果A不想将C依赖进来可以同时排除C
## 开发规范
### REST风格
在前后端进行交互的时候我们需要基于当前主流的REST风格的API接口进行交互。
@ -377,6 +379,16 @@ public class DeptController {
**GET**:查询,用 URL 传参,不能带 body。
**POST**:创建/提交,可以用 body 传数据JSON、表单
**PUT**:更新,可以用 body 。
**DELETE**:删除,一般无 body只要 `-X DELETE`
### 开发流程
![image-20220904125004138](https://pic.bitday.top/i/2025/03/19/u6qh4d-2.png)
@ -520,7 +532,9 @@ public class RequestController {
![image-20240303112109981](https://pic.bitday.top/i/2025/03/19/u6ndfm-2.png)
注意这里User前面不能加`@RequestBody`是因为请求方式是 (表单)或 URL 参数如果是JSON请求体就必须加。
注意这里User前面不能加`@RequestBody`是因为请求方式是 **Query****路径** 参数如果是JSON请求体**Body**)就必须加。
<img src="https://pic.bitday.top/i/2025/04/15/nxkm0y-0.png" alt="image-20250415144710544" style="zoom:80%;" />
```java
@RequestMapping("/complexpojo")
@ -575,15 +589,13 @@ public class RequestController {
#### 路径参数
在现在的开发中,经常还会直接在请求的URL中传递参数。例如
请求的URL中传递的参数 称为路径参数。例如:
~~~text
http://localhost:8080/user/1
http://localhost:880/user/1/0
~~~
上述的这种传递请求参数的形式呢,我们称之为:路径参数。
注意,路径参数使用大括号 `{}` 定义
```java
@ -598,12 +610,12 @@ public class RequestController {
}
```
在路由定义里用 `{id}` 只是一个占位符,实际请求时 **不要** 带大括号
#### JSON格式参数
```java
{
"backtime": [
@ -622,6 +634,9 @@ public class RequestController {
**JSON 格式的核心特征**
- 接口文档中的请求参数中是 **'Body'** 发送数据
<img src="https://pic.bitday.top/i/2025/04/15/nwm01g-0.png" alt="image-20250415144541146" style="zoom:80%;" />
- 数据为键值对:数据存储在键值对中,键和值用冒号分隔。在你的示例中,每个对象有两个键值对,如 `"firstName": "John"`
- 使用大括号表示对象JSON 使用大括号 `{}` 包围对象,对象可以包含多个键值对。
- 使用方括号表示数组JSON 使用方括号 `[]` 表示数组,数组中可以包含多个值,包括数字、字符串、对象等。在该示例中:`"employees"` 是一个对象数组,数组中的每个元素都是一个对象。
@ -1141,7 +1156,7 @@ public class XmlProcessingService {
// 按类型注入
@Autowired
private SAXReader reader;
private SAXReader reader; //方法的名字!!
public void parse(String xmlPath) throws DocumentException {
Document doc = reader.read(new File(xmlPath));
@ -1154,6 +1169,8 @@ public class XmlProcessingService {
### SpirngBoot原理
如果我们直接基于Spring框架进行项目的开发会比较繁琐。SpringBoot框架之所以使用起来更简单更快捷是因为SpringBoot框架底层提供了两个非常重要的功能一个是起步依赖一个是自动配置。
#### **起步依赖**
Spring Boot 只需要引入一个起步依赖(例如 `springboot-starter-web`)就能满足项目开发需求。这是因为:
@ -1174,24 +1191,31 @@ Spring Boot 会自动扫描启动类**所在包及其子包中**的所有带有
自动配置原理源码入口就是@SpringBootApplication注解在这个注解中封装了3个注解分别是
- @SpringBootConfiguration
- 声明当前类是一个配置类
- 声明当前类是一个配置类,等价于 `@Configuration`又与之区分
- @**ComponentScan**
- 进行组件扫描SpringBoot中默认扫描的是启动类所在的当前包及其子包
- @EnableAutoConfiguration
- 内部使用 `@Import` 导入一个 `ImportSelector` 实现类,该实现类在其 `selectImports()` 方法中读取依赖 jar 包中 META-INF 下的配置文件(如 `spring.factories`),获取自动配置类列表。最终,通过 `@Import` 将这些配置类加载到 Spring 的 IoC 容器中。
- 进行组件扫描。如果你的项目有server pojo common模块启动类在`com.your.package.server`下,那么只会默认扫描`com.your.package`及其子包。
- `@ComponentScan({"com.your.package.server", "com.your.package.common"})`可以显示指定扫描的包路径。
- @**EnableAutoConfiguration**(自动配置核心注解,下节详解)
**自动配置类中的 Bean 加载**
**自动配置的效果**
- 自动配置类定义的所有 Bean **不一定**全部加载到容器中。
- 通常会配合使用以 `@Conditional` 开头的条件注解,根据环境或依赖条件决定是否装配对应的 Bean从而实现有选择的配置。
![image-20230114221341811](https://pic.bitday.top/i/2025/04/16/qzqw3j-0.png)
在IOC容器中除了我们自己定义的bean以外还有很多配置类这些配置类都是SpringBoot在启动的时候加载进来的配置类。这些配置类加载进来之后它也会生成很多的bean对象。
当我们想要使用这些配置类中生成的bean对象时可以使用@Autowired就自动注入了
**如何让第三方bean以及配置类生效**
如果配置类(如 `CommonConfig`)不在 Spring Boot 启动类的扫描路径内(即不在启动类所在包或其子包下),那么就需要通过 `@Import` 手动导入该配置类。如:
如果配置类(如 `CommonConfig`)不在 Spring Boot 启动类的扫描路径内(即不在启动类所在包或其子包下):
1.`@ComponentScan`添加包扫描路径,适合批量导入(繁琐、性能低)
2.通过 `@Import` 手动导入该配置类。适合精确导入,如:
```java
com
@ -1203,7 +1227,9 @@ src
└── CommonConfig.java // 配置类
```
借助 `@Import` 注解,我们可以将外部的普通类、配置类或实现了 `ImportSelector` 的类**显式导入**到 Spring 容器中。
借助 `@Import` 注解,我们可以将外部的普通类、配置类或实现了 `ImportSelector` 的类**显式导入**到 Spring 容器中。 也就是这些类会加载到IOC容器中。
**1.使用@Import导入普通类**
@ -1290,6 +1316,29 @@ public class SpringbootWebConfig2Application {
#### @EnableAutoConfiguration
**导入自动配置类**
- 通过元注解 `@Import(AutoConfigurationImportSelector.class)`,在启动时读取所有 JAR 包中 `METAINF/spring.factories``EnableAutoConfiguration` 对应的自动配置类列表。
- 将这些配置类当作 `@Configuration` 导入到 Spring 容器中。
**按条件注册 Bean**
- 自动配置类内部使用多种条件注解(如 `@ConditionalOnClass``@ConditionalOnMissingBean``@ConditionalOnProperty` 等)。
- Spring Boot 会检查当前类路径、配置属性和已有 Bean**仅在满足所有条件时**,才执行对应的 `@Bean` 方法,将组件注入 IOC 容器。
`@ComponentScan` 用于发现和加载应用自身的组件;
`@EnableAutoConfiguration` 则负责加载 SpringBoot 提供的“开箱即用”配置。如:
- `DataSourceAutoConfiguration` 检测到常见的 JDBC 驱动(如 HikariCP、Tomcat JDBC和配置属性`spring.datasource.*`)时,自动创建并配置 `DataSource`。、
- `WebMvcAutoConfiguration`自动配置 Spring MVC 的核心组件并启用默认的静态资源映射、消息转换器Jackson JSON等。但遇到用户自定义的 MVC 支持配置(如继承 `WebMvcConfigurationSupport` 时会“失效”Back Off因为其内部有个注解`@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)`一旦容器内有xx类型注解默认配置自动失效。
## 常见的注解!!
1. `@RequestMapping("/jsonParam")`:可以用于**控制器级别**,也可以用于**方法级别**。
@ -1417,6 +1466,20 @@ public class SpringbootWebConfig2Application {
System.out.println(addr1.equals(addr2)); // 输出 true
```
log:
```java
log.info("应用启动成功");
Long empId = 12L;
log.info("当前员工id{}", empId); //带占位符,推荐!
log.info("当前员工id" + empId); //不错,但不推荐
log.info("当前员工id", empId); //错误的!
```
10. `@Test`Junit测试单元可在测试类中定义测试函数一次性执行所有@Test注解下的函数不用写main方法
11. `@Override`,当一个方法在子类中覆盖(重写)了父类中的同名方法时,为了确保正确性,可以使用 `@Override` 注解来标记这个方法,这样编译器就能够帮助检查是否正确地重写了父类的方法。
@ -1535,7 +1598,7 @@ Druid德鲁伊
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql://localhost:3306/mybatis
spring.datasource.druid.username=root
spring.datasource.druid.password=1234
spring.datasource.druid.password=123456
```
@ -1610,11 +1673,22 @@ mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
> - 表中字段名abc_xyz
> - 类中属性名abcXyz
```java
# 在application.properties中添加
mybatis.configuration.map-underscore-to-camel-case=true
### 推荐的完整配置:
```yaml
mybatis:
#mapper配置文件
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.sky.entity
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true
```
`type-aliases-package: com.sky.entity``com.sky.entity` 包下的所有类都当作别名注册XML 里就可以直接写 `<resultType="Dish">` 而不用写全限定名。可以多添加几个包,用逗号隔开。
### 增删改
@ -1720,7 +1794,15 @@ public interface EmpMapper {
\<select>标签就是用于编写select查询语句的。
resultType属性指的是查询返回的单条记录所封装的类型。
resultType属性指的是查询返回的单条记录所封装的类型(查询必须)。
parameterType属性可选MyBatis 会根据接口方法的入参类型(比如 `Dish``DishPageQueryDTO`自动推断POJO作为入参需要使用全类名或是`typealiasespackage: com.sky.entity` 下注册的别名。
```
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
<select id="pageQuery" resultType="com.sky.vo.DishVO">
<select id="list" resultType="com.sky.entity.Dish" parameterType="com.sky.entity.Dish">
```
@ -1842,294 +1924,6 @@ XML 映射文件
## 案例实战
### 分页查询
传统员工分页查询分析:
![image-20221215153413290](https://pic.bitday.top/i/2025/03/19/u6p5ae-2.png)
**采用分页插件PageHelper**
![image-20221215170038833](https://pic.bitday.top/i/2025/03/19/u6o1ho-2.png)
**在执行empMapper.list()方法时就是执行select * from emp 语句,怎么能够实现分页操作呢?**
分页插件帮我们完成了以下操作:
1. 先获取到要执行的SQL语句
```
select * from emp
```
2. 为了实现分页,第一步是获取符合条件的总记录数。分页插件将原始 SQL 查询中的 `SELECT *` 改成 `SELECT count(*)`
```mysql
select count(*) from emp;
```
3. 一旦知道了总记录数,分页插件会将 `SELECT *` 的查询语句进行修改,加入 `LIMIT` 关键字,限制返回的记录数。
```mysql
select * from emp limit ?, ?
```
第一个参数(`?`)是 起始位置,通常是 `(当前页 - 1) * 每页显示的记录数`,即从哪一行开始查询。
第二个参数(`?`)是 每页显示的记录数,即返回多少条数据。
4. 执行分页查询,例如,假设每页显示 10 条记录,你请求第 2 页数据,那么 SQL 语句会变成:
```mysql
select * from emp limit 10, 10;
```
**使用方法:**
当使用 **PageHelper** 分页插件时,无需在 Mapper 中手动处理分页。只需在 Mapper 中编写常规的列表查询。
- 在 **Service 层**,调用 Mapper 方法之前,**设置分页参数**。
- 调用 Mapper 查询后,**自动进行分页**,并将结果封装到 `PageBean` 对象中返回。
1、在pom.xml引入依赖
```java
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
```
2、EmpMapper
```java
@Mapper
public interface EmpMapper {
//获取当前页的结果列表
@Select("select * from emp")
public List<Emp> list();
}
```
3、EmpServiceImpl
当调用 `PageHelper.startPage(page, pageSize)`PageHelper 插件会拦截随后的 SQL 查询,自动修改查询,加入 `LIMIT` 子句来实现分页功能。
```java
@Override
public PageBean page(Integer page, Integer pageSize) {
// 设置分页参数
PageHelper.startPage(page, pageSize); //page是页号不是起始索引
// 执行分页查询
List<Emp> empList = empMapper.list();
// 获取分页结果
Page<Emp> p = (Page<Emp>) empList;
//封装PageBean
PageBean pageBean = new PageBean(p.getTotal(), p.getResult());
return pageBean;
}
```
4、Controller
```java
@Slf4j
@RestController
@RequestMapping("/emps")
public class EmpController {
@Autowired
private EmpService empService;
//条件分页查询
@GetMapping
public Result page(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize) {
//记录日志
log.info("分页查询,参数:{},{}", page, pageSize);
//调用业务层分页查询功能
PageBean pageBean = empService.page(page, pageSize);
//响应
return Result.success(pageBean);
}
}
```
### 条件分页查询
思路分析:
![image-20221215180528415](https://pic.bitday.top/i/2025/03/19/u6mjck-2.png)
```java
<select id="pageQuery" resultType="com.sky.entity.Employee">
select * from employee
<where>
<if test="name != null and name != ''">
and name like concat('%',#{name},'%')
</if>
</where>
order by create_time desc
/select>
```
### 文件上传
#### 本地存储
文件上传时在服务端会产生一个临时文件,请求响应完成之后,这个临时文件被自动删除,并没有进行保存。下面呢,我们就需要完成将上传的文件保存在服务器的本地磁盘上。
代码实现:
1. 在服务器本地磁盘上创建images目录用来存储上传的文件E盘创建images目录
2. 使用MultipartFile类提供的API方法把临时文件转存到本地磁盘目录下
> MultipartFile 常见方法:
>
> - String getOriginalFilename(); //获取原始文件名
> - void transferTo(File dest); //将接收的文件转存到磁盘文件中
> - long getSize(); //获取文件的大小,单位:字节
> - byte[] getBytes(); //获取文件内容的字节数组
> - InputStream getInputStream(); //获取接收到的文件内容的输入流
```java
@Slf4j
@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(String username, Integer age, MultipartFile image) throws IOException {
log.info("文件上传:{},{},{}",username,age,image);
//获取原始文件名
String originalFilename = image.getOriginalFilename();
//构建新的文件名
String extname = originalFilename.substring(originalFilename.lastIndexOf("."));//文件扩展名
String newFileName = UUID.randomUUID().toString()+extname;//随机名+文件扩展名
//将文件存储在服务器的磁盘目录
image.transferTo(new File("E:/images/"+newFileName));
return Result.success();
}
}
```
在SpringBoot中文件上传时默认单个文件最大大小为1M
那么如果需要上传大文件可以在application.properties进行如下配置
```java
#配置单个文件最大上传大小
spring.servlet.multipart.max-file-size=10MB
#配置单个请求最大上传大小(一次请求可以上传多个文件)
spring.servlet.multipart.max-request-size=100MB
```
**不推荐!**
#### 阿里云OSS存储
pom文件中添加如下依赖
```java
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
```
上传文件的工具类
```java
package edu.whut.utils;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.aliyuncs.exceptions.ClientException;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.UUID;
/**
* 阿里云 OSS 工具类
*/
@Component
public class AliOSSUtils {
private String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
private String bucketName = "zyjavaweb";
/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile file) throws IOException, ClientException {
InputStream inputStream = file.getInputStream();
// 避免文件覆盖
String originalFilename = file.getOriginalFilename();
String extname = originalFilename.substring(originalFilename.lastIndexOf("."));//文件扩展名
String fileName = UUID.randomUUID().toString() + extname;
//上传文件到 OSS
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileName, inputStream);
PutObjectResult result = ossClient.putObject(putObjectRequest);
//文件访问路径
String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
// 关闭ossClient
ossClient.shutdown();
return url;// 把上传到oss的路径返回
}
}
```
使用时传入MultipartFile类型的文件
## 登录校验
### 会话技术
@ -2422,6 +2216,10 @@ String username = (String) claims.get("username");
4. 服务端校验令牌
服务端接收到请求后,**拦截请求并检查是否携带令牌**。若没有令牌,拒绝访问;若令牌存在,校验令牌的**有效性**(包括有效期),若有效则放行,进行请求处理。
注意使用APIFOX测试时需要在headers中添加
{token:"jwt令牌..."}否则会无法通过拦截器。
### 拦截器(Interceptor)

View File

@ -185,6 +185,83 @@ Math.min(a, b));
#### 枚举
```java
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
OperationType opType = OperationType.INSERT; // 声明并初始化
public void execute(OperationType type, Object entity) {
switch (type) {
case INSERT:
insertEntity(entity);
break;
case UPDATE:
updateEntity(entity);
break;
default:
throw new IllegalArgumentException("Unsupported operation: " + type);
}
}
```
```java
// 定义枚举类型
public enum DayOfWeek {
MONDAY("星期一", 1),
TUESDAY("星期二", 2),
WEDNESDAY("星期三", 3),
THURSDAY("星期四", 4),
FRIDAY("星期五", 5),
SATURDAY("星期六", 6),
SUNDAY("星期日", 7);
// 枚举属性
private final String chineseName;
private final int dayNumber;
// 构造方法
DayOfWeek(String chineseName, int dayNumber) {
this.chineseName = chineseName;
this.dayNumber = dayNumber;
}
// 方法
public String getChineseName() {
return chineseName;
}
public int getDayNumber() {
return dayNumber;
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
DayOfWeek today = DayOfWeek.MONDAY;
System.out.println(today.getChineseName()); // 输出: 星期一
System.out.println(today.getDayNumber()); // 输出: 1
}
}
```
#### Java传参方式
基本数据类型Primitives
@ -1885,3 +1962,16 @@ public class AnnotationTest4 {
}
```
employeeDTO的内容拷贝给employee
```java
StartOrStopDTO dto = new StartOrStopDTO(1, 100L);
// 用 Builder 拷贝 id 和 status
Employee employee = Employee.builder()
.id(dto.getId())
.status(dto.getStatus())
.build();
```

View File

@ -452,7 +452,293 @@ public class EmployeeController {
## 开发
## 实战开发
### 分页查询
传统员工分页查询分析:
![image-20221215153413290](https://pic.bitday.top/i/2025/03/19/u6p5ae-2.png)
**采用分页插件PageHelper**
![image-20221215170038833](https://pic.bitday.top/i/2025/03/19/u6o1ho-2.png)
**在执行empMapper.list()方法时就是执行select * from emp 语句,怎么能够实现分页操作呢?**
分页插件帮我们完成了以下操作:
1. 先获取到要执行的SQL语句
```
select * from emp
```
2. 为了实现分页,第一步是获取符合条件的总记录数。分页插件将原始 SQL 查询中的 `SELECT *` 改成 `SELECT count(*)`
```mysql
select count(*) from emp;
```
3. 一旦知道了总记录数,分页插件会将 `SELECT *` 的查询语句进行修改,加入 `LIMIT` 关键字,限制返回的记录数。
```mysql
select * from emp limit ?, ?
```
第一个参数(`?`)是 起始位置,通常是 `(当前页 - 1) * 每页显示的记录数`,即从哪一行开始查询。
第二个参数(`?`)是 每页显示的记录数,即返回多少条数据。
4. 执行分页查询,例如,假设每页显示 10 条记录,你请求第 2 页数据,那么 SQL 语句会变成:
```mysql
select * from emp limit 10, 10;
```
**使用方法:**
当使用 **PageHelper** 分页插件时,无需在 Mapper 中手动处理分页。只需在 Mapper 中编写常规的列表查询。
- 在 **Service 层**,调用 Mapper 方法之前,**设置分页参数**。
- 调用 Mapper 查询后,**自动进行分页**,并将结果封装到 `PageBean` 对象中返回。
1、在pom.xml引入依赖
```java
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
```
2、EmpMapper
```java
@Mapper
public interface EmpMapper {
//获取当前页的结果列表
@Select("select * from emp")
public List<Emp> list();
}
```
3、EmpServiceImpl
当调用 `PageHelper.startPage(page, pageSize)`PageHelper 插件会拦截随后的 SQL 查询,自动修改查询,加入 `LIMIT` 子句来实现分页功能。
```java
@Override
public PageBean page(Integer page, Integer pageSize) {
// 设置分页参数
PageHelper.startPage(page, pageSize); //page是页号不是起始索引
// 执行分页查询
List<Emp> empList = empMapper.list();
// 获取分页结果
Page<Emp> p = (Page<Emp>) empList;
//封装PageBean
PageBean pageBean = new PageBean(p.getTotal(), p.getResult());
return pageBean;
}
```
4、Controller
```java
@Slf4j
@RestController
@RequestMapping("/emps")
public class EmpController {
@Autowired
private EmpService empService;
//条件分页查询
@GetMapping
public Result page(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize) {
//记录日志
log.info("分页查询,参数:{},{}", page, pageSize);
//调用业务层分页查询功能
PageBean pageBean = empService.page(page, pageSize);
//响应
return Result.success(pageBean);
}
}
```
### 条件分页查询
思路分析:
![image-20221215180528415](https://pic.bitday.top/i/2025/03/19/u6mjck-2.png)
```java
<select id="pageQuery" resultType="com.sky.entity.Employee">
select * from employee
<where>
<if test="name != null and name != ''">
and name like concat('%',#{name},'%')
</if>
</where>
order by create_time desc
/select>
```
### 文件上传
#### 本地存储
文件上传时在服务端会产生一个临时文件,请求响应完成之后,这个临时文件被自动删除,并没有进行保存。下面呢,我们就需要完成将上传的文件保存在服务器的本地磁盘上。
代码实现:
1. 在服务器本地磁盘上创建images目录用来存储上传的文件E盘创建images目录
2. 使用MultipartFile类提供的API方法把临时文件转存到本地磁盘目录下
> MultipartFile 常见方法:
>
> - String getOriginalFilename(); //获取原始文件名
> - void transferTo(File dest); //将接收的文件转存到磁盘文件中
> - long getSize(); //获取文件的大小,单位:字节
> - byte[] getBytes(); //获取文件内容的字节数组
> - InputStream getInputStream(); //获取接收到的文件内容的输入流
```java
@Slf4j
@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(String username, Integer age, MultipartFile image) throws IOException {
log.info("文件上传:{},{},{}",username,age,image);
//获取原始文件名
String originalFilename = image.getOriginalFilename();
//构建新的文件名
String extname = originalFilename.substring(originalFilename.lastIndexOf("."));//文件扩展名
String newFileName = UUID.randomUUID().toString()+extname;//随机名+文件扩展名
//将文件存储在服务器的磁盘目录
image.transferTo(new File("E:/images/"+newFileName));
return Result.success();
}
}
```
在SpringBoot中文件上传时默认单个文件最大大小为1M
那么如果需要上传大文件可以在application.properties进行如下配置
```java
#配置单个文件最大上传大小
spring.servlet.multipart.max-file-size=10MB
#配置单个请求最大上传大小(一次请求可以上传多个文件)
spring.servlet.multipart.max-request-size=100MB
```
**不推荐!**
#### 阿里云OSS存储
pom文件中添加如下依赖
```java
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
```
上传文件的工具类
```java
package edu.whut.utils;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.aliyuncs.exceptions.ClientException;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.UUID;
/**
* 阿里云 OSS 工具类
*/
@Component
public class AliOSSUtils {
private String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
private String bucketName = "zyjavaweb";
/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile file) throws IOException, ClientException {
InputStream inputStream = file.getInputStream();
// 避免文件覆盖
String originalFilename = file.getOriginalFilename();
String extname = originalFilename.substring(originalFilename.lastIndexOf("."));//文件扩展名
String fileName = UUID.randomUUID().toString() + extname;
//上传文件到 OSS
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileName, inputStream);
PutObjectResult result = ossClient.putObject(putObjectRequest);
//文件访问路径
String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
// 关闭ossClient
ossClient.shutdown();
return url;// 把上传到oss的路径返回
}
}
```
使用时传入MultipartFile类型的文件
### 加密算法
@ -522,7 +808,7 @@ boolean judge= passwordEncoder.matches(rawPassword, user.getPassword());
**问题2**
如何获得当前登录的管理员id==》拦截器中解析的token中的id怎么传入controller里
如何获得当前登录的用户id
方法ThreadLocal
@ -550,14 +836,23 @@ public class BaseContext {
}
```
实现方式:登录的时候BaseContext.setCurrentId(id);
实现方式:登录成功 -> 生成jwt令牌 (claims中存userId)->前端浏览器保存
要用的时候直接BaseContext.getCurrentId();
后续每次请求携带jwt -> 拦截器检查jwt令牌 -> BaseContext.setCurrentId(jwt中取出的userId); ->
BaseContext.getCurrentId(); //service层中获取当前userId
### SpringMVC的消息转换器(处理日期)
**Jackson** 是一个用于处理 **JSON 数据** 的流行 Java 库,主要用于:
1. **序列化**:将 Java 对象转换为 JSON 字符串(例如:`Java对象 → {"name":"Alice"}`)。
2. **反序列化**:将 JSON 字符串解析为 Java 对象(例如:`{"name":"Alice"} → Java对象`)。
**Spring Boot**默认集成了Jackson
**1). 方式一**
在属性上加上注解,对日期进行格式化
@ -570,11 +865,6 @@ public class BaseContext {
在**WebMvcConfiguration**中扩展SpringMVC的消息转换器统一对日期类型进行格式处理
作用:
1. **请求数据转换(反序列化)**当服务器接收到一个HTTP请求时消息转换器将请求体中的数据如JSON、XML等格式转换成控制器Controller方法参数所期望的Java对象。这个过程称为反序列化。
2. **响应数据转换(序列化)**当控制器处理完业务逻辑后需要将结果数据返回给客户端。消息转换器此时将Java对象序列化为客户端可识别的格式如JSON、XML等并包装在HTTP响应体中发送。
```java
/**
* 扩展Spring MVC框架的消息转化器
@ -586,7 +876,7 @@ public class BaseContext {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//需要为消息转换器设置一个对象转换器对象转换器可以将Java对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转化器加入容器中
//将自己的消息转化器加入容器中,确保覆盖默认的 Jackson 行为
converters.add(0,converter);
}
```
@ -596,12 +886,13 @@ public class BaseContext {
JacksonObjectMapper()文件:
```java
//直接复用 Jackson 的核心功能,仅覆盖或扩展特定行为。
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_FORMAT = "yyyy-MM-dd"; //LocalDate
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; //LocalDateTime
// public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss"; //LocalTime
public JacksonObjectMapper() {
super();
@ -627,32 +918,6 @@ public class JacksonObjectMapper extends ObjectMapper {
### 构造实体对象的两种方法
```java
public void startOrStop(Integer status, Long id) {
//法一
Employee employee = Employee.builder()
.status(status)
.id(id)
.build();
//法二
//Employee employee=new Employee();
//employee.setStatus(status);
//employee.setId(id);
}
```
还有一种把源对象source的属性值赋给目标**对象**target中与源**对象**source的中有着同属性名的属性
```java
BeanUtils.copyProperties(source,target);
```
### 修改员工信息(复用update方法)
代码能复用尽量复用在mapper类里定义一个**通用的update**接口即mybatis操作数据库时修改员工信息都调用这个接口。**启用/禁用员工**可能只要修改status**修改员工**可能大面积修改属性,在**mapper**类中定义一个通用的update方法但是**controller层和service层**的函数命名可以不一样,以区分两种业务。
@ -693,7 +958,11 @@ BeanUtils.copyProperties(source,target);
### 公共字段自动填充——AOP编程
在实现公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。在上述的问题分析中,我们提到有四个公共字段,需要在新增/更新中进行赋值操作, 具体情况如下:
在数据库操作中通常需要为某些公共字段如创建时间、更新时间等自动赋值。采用AOP
1. 统一管理这些字段的赋值逻辑
2. 避免在业务代码中重复设置
3. 确保数据一致性
| **序号** | **字段名** | **含义** | **数据类型** | **操作类型** |
| -------- | ----------- | -------- | ------------ | -------------- |
@ -714,8 +983,6 @@ BeanUtils.copyProperties(source,target);
若要实现上述步骤,需掌握以下知识(之前课程内容都学过)
**技术点:**枚举、注解、AOP、反射