From 09893030dff1489a479372b8c036e3672ce367be Mon Sep 17 00:00:00 2001 From: zhangsan <646228430@qq.com> Date: Wed, 16 Apr 2025 18:02:24 +0800 Subject: [PATCH] =?UTF-8?q?Commit=20on=202025/04/16=20=E5=91=A8=E4=B8=89?= =?UTF-8?q?=2018:02:24.06?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 自学/JavaWeb——后端.md | 418 +++++++++++------------------------------- 自学/Java笔记本.md | 90 +++++++++ 自学/苍穹外卖.md | 351 ++++++++++++++++++++++++++++++----- 3 files changed, 507 insertions(+), 352 deletions(-) diff --git a/自学/JavaWeb——后端.md b/自学/JavaWeb——后端.md index 4299ae9..1917b46 100644 --- a/自学/JavaWeb——后端.md +++ b/自学/JavaWeb——后端.md @@ -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依赖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`。 + + + ### 开发流程 ![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**)就必须加。 + +image-20250415144710544 ```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'** 发送数据 + image-20250415144541146 + - 数据为键值对:数据存储在键值对中,键和值用冒号分隔。在你的示例中,每个对象有两个键值对,如 `"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 包中 `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 里就可以直接写 `` 而不用写全限定名。可以多添加几个包,用逗号隔开。 + ### 增删改 @@ -1720,7 +1794,15 @@ public interface EmpMapper { \ + - select * from employee - - - and name like concat('%',#{name},'%') - - - 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 - - com.aliyun.oss - aliyun-sdk-oss - 3.15.1 - - - javax.xml.bind - jaxb-api - 2.3.1 - - - javax.activation - activation - 1.1.1 - - - - org.glassfish.jaxb - jaxb-runtime - 2.3.3 - -``` - - - -上传文件的工具类 - -```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) diff --git a/自学/Java笔记本.md b/自学/Java笔记本.md index 40c764d..600c9b7 100644 --- a/自学/Java笔记本.md +++ b/自学/Java笔记本.md @@ -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(); +``` + diff --git a/自学/苍穹外卖.md b/自学/苍穹外卖.md index 0f8332e..deb4358 100644 --- a/自学/苍穹外卖.md +++ b/自学/苍穹外卖.md @@ -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 + + com.github.pagehelper + pagehelper-spring-boot-starter + 1.4.2 + +``` + +2、EmpMapper + +```java +@Mapper +public interface EmpMapper { + //获取当前页的结果列表 + @Select("select * from emp") + public List 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 empList = empMapper.list(); + // 获取分页结果 + Page p = (Page) 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 +