Commit on 2025/04/16 周三 18:02:24.06
This commit is contained in:
parent
e9e0a05fe4
commit
09893030df
@ -178,7 +178,7 @@ Maven 重建
|
||||
|
||||
什么是坐标?
|
||||
|
||||
* Maven中的坐标是==资源的唯一标识== , 通过该坐标可以唯一定位资源位置
|
||||
* Maven中的坐标是 == 资源的唯一标识 == 通过该坐标可以唯一定位资源位置
|
||||
* 使用坐标来定义项目或引入项目中需要的依赖
|
||||
|
||||

|
||||
@ -319,6 +319,8 @@ A依赖B,B依赖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`。
|
||||
|
||||
|
||||
|
||||
### 开发流程
|
||||
|
||||

|
||||
@ -520,7 +532,9 @@ public class RequestController {
|
||||
|
||||

|
||||
|
||||
注意:这里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,从而实现有选择的配置。
|
||||

|
||||
|
||||
在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 包中 `META‑INF/spring.factories` 下 `EnableAutoConfiguration` 对应的自动配置类列表。
|
||||
- 将这些配置类当作 `@Configuration` 导入到 Spring 容器中。
|
||||
|
||||
**按条件注册 Bean**
|
||||
|
||||
- 自动配置类内部使用多种条件注解(如 `@ConditionalOnClass`、`@ConditionalOnMissingBean`、`@ConditionalOnProperty` 等)。
|
||||
- Spring Boot 会检查当前类路径、配置属性和已有 Bean,**仅在满足所有条件时**,才执行对应的 `@Bean` 方法,将组件注入 IOC 容器。
|
||||
|
||||
|
||||
|
||||
`@ComponentScan` 用于发现和加载应用自身的组件;
|
||||
|
||||
`@EnableAutoConfiguration` 则负责加载 Spring Boot 提供的“开箱即用”配置。如:
|
||||
|
||||
- `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作为入参,需要使用全类名或是`type‑aliases‑package: 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 映射文件
|
||||
|
||||
|
||||
|
||||
## 案例实战
|
||||
|
||||
### 分页查询
|
||||
|
||||
传统员工分页查询分析:
|
||||
|
||||

|
||||
|
||||
**采用分页插件PageHelper:**
|
||||
|
||||

|
||||
|
||||
**在执行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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 条件分页查询
|
||||
|
||||
思路分析:
|
||||
|
||||

|
||||
|
||||
```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)
|
||||
|
@ -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();
|
||||
```
|
||||
|
||||
|
351
自学/苍穹外卖.md
351
自学/苍穹外卖.md
@ -452,7 +452,293 @@ public class EmployeeController {
|
||||
|
||||
|
||||
|
||||
## 开发
|
||||
## 实战开发
|
||||
|
||||
### 分页查询
|
||||
|
||||
传统员工分页查询分析:
|
||||
|
||||

|
||||
|
||||
**采用分页插件PageHelper:**
|
||||
|
||||

|
||||
|
||||
**在执行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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 条件分页查询
|
||||
|
||||
思路分析:
|
||||
|
||||

|
||||
|
||||
```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、反射
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user