feat:增加字体

This commit is contained in:
2025-11-27 14:44:19 +08:00
parent c064dfc417
commit daab0c4495
21 changed files with 1045 additions and 125 deletions

View File

@@ -2,8 +2,11 @@ import { defineConfig } from 'vitepress'
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "Cxx0822",
title: "拾光记",
description: "Cxx0822 Blog",
head: [
['link', { rel: 'icon', href: '/blog-press/favicon.ico' }]
],
base: '/blog-press/',
lang: 'zh-CN',
locales: {
@@ -13,19 +16,22 @@ export default defineConfig({
},
},
themeConfig: {
logo: "/logo.png",
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Home', link: '/' },
{ text: 'Web', link: '/Web/' },
{ text: 'Web后端', link: '/Web-Backend/' },
{ text: 'DevOps', link: '/DevOps/' },
{ text: 'SpringBoot', link: '/SpringBoot/' }
{ text: 'SpringBoot', link: '/SpringBoot/' },
],
sidebar: {
'/Web/': [
'/Web-Backend/': [
{
text: 'Web',
text: 'Web后端',
items: [
{ text: 'MySQL知识点', link: '/Web/MySQL-Knowledge' }
{ text: 'MySQL知识点', link: '/Web-Backend/MySQL-Knowledge' },
{ text: 'Flyway简单使用', link: '/Web-Backend/Flyway' },
{ text: 'MyBatis简介和使用', link: '/Web-Backend/MyBatis' }
]
},
],
@@ -44,7 +50,11 @@ export default defineConfig({
text: 'SpringBoot',
items: [
{ text: 'Spring IOC简介', link: '/SpringBoot/SpringIOC' },
{ text: 'SpringBoot启动流程', link: '/SpringBoot/SpringBoot-Start-Process' }
{ text: 'SpringBoot启动流程', link: '/SpringBoot/SpringBoot-Start-Process' },
{ text: 'Spring MVC简介', link: '/SpringBoot/SpringMVC' },
{ text: 'SpringBoot Starter原理', link: '/SpringBoot/SpringBootStarter' },
{ text: 'SpringBoot Bean简介', link: '/SpringBoot/SpringBootBean' },
{ text: 'SpringBoot3原生镜像', link: '/SpringBoot/SpringBoot3-GraalVM' }
]
}
]
@@ -93,6 +103,9 @@ export default defineConfig({
next: '下一篇'
},
sidebarMenuLabel: '菜单',
darkModeSwitchLabel: '外观',
lastUpdated: {
text: '最后更新于'
}

View File

@@ -0,0 +1,27 @@
<template>
<div class="custom-component">
<h3>{{ title }}</h3>
<p>{{ description }}</p>
<button @click="count++">点击次数: {{ count }}</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
title: String,
description: String
})
const count = ref(0)
</script>
<style scoped>
.custom-component {
border: 1px solid #e2e2e2;
padding: 1rem;
border-radius: 8px;
margin: 1rem 0;
}
</style>

Binary file not shown.

View File

@@ -0,0 +1,12 @@
import DefaultTheme from 'vitepress/theme'
import '../theme/style.css'
// 定义组件导入的类型
interface ComponentModule {
default: any
}
export default {
extends: DefaultTheme
}

View File

@@ -0,0 +1,19 @@
@font-face {
font-family: 'CustomFont';
src: url('./fonts/custom.woff2') format('woff2');
font-display: swap;
}
:root {
--vp-font-family-base: 'CustomFont', -apple-system, BlinkMacSystemFont,
'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
--vp-font-family-mono: 'CustomFont', 'Fira Code', 'Consolas', monospace;
}
:root {
--vp-c-brand: #10b981;
--vp-c-brand-light: #34d399;
--vp-c-brand-lighter: #6ee7b7;
--vp-c-brand-dark: #059669;
--vp-c-brand-darker: #047857;
}

View File

@@ -50,24 +50,24 @@ volumes:
driver: local
```
&emsp;&emsp;elasticsearch和kibana版本要保持一致该镜像较大可以先使用docker pull拉取到本地。
&emsp;&emsp;ELASTIC_PASSWORD和KIBANA_PASSWORD可以通过.env配置文件设置。
&emsp;&emsp;启动时elasticsearch可能会提示elasticsearch-data文件夹权限问题目前解决方案为将其权限设置为777。
&emsp;&emsp;elasticsearch和kibana版本要保持一致该镜像较大可以先使用docker pull拉取到本地。
&emsp;&emsp;ELASTIC_PASSWORD和KIBANA_PASSWORD可以通过.env配置文件设置。
&emsp;&emsp;启动时elasticsearch可能会提示elasticsearch-data文件夹权限问题目前解决方案为将其权限设置为777。
## 三、简单使用
### 3.1 安装插件
&emsp;&emsp;启动成功后需要安装elasticsearch的分词插件analysis-ik。
&emsp;&emsp;启动成功后需要安装elasticsearch的分词插件analysis-ik。
&emsp;&emsp;进入Docker容器输入指令
```cmd
bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/8.12.0
bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/8.12.0
```
&emsp;&emsp;注意版本要匹配。https://github.com/infinilabs/analysis-ik/releases/tag/Latest
&emsp;&emsp;注意版本要匹配。https://github.com/infinilabs/analysis-ik/releases/tag/Latest
&emsp;&emsp;安装完成后,重启容器。
### 3.2 更改密码
&emsp;&emsp;启动成功后需要更改elasticsearch的账号密码不知道为啥docker-compose文件设置的密码不行可能是容器初始化后又重置了
&emsp;&emsp;启动成功后需要更改elasticsearch的账号密码不知道为啥docker-compose文件设置的密码不行可能是容器初始化后又重置了
&emsp;&emsp;进入Docker容器输入指令
```cmd
bin/elasticsearch-setup-passwords interactive
@@ -76,7 +76,7 @@ bin/elasticsearch-setup-passwords interactive
&emsp;&emsp;根据提示输入相应的账号密码。
### 3.3 新建索引
1. 打开Elastic控制台选择Management->Dev Tools输入语句新建索引。
2. 选择Management->Stack Management->Data Views选择Create data view新建数据视图。
3. 输入name、index pattern刚才新建的索引和Timestamp信息保存数据视图。
4. 选择Discover选择刚才的数据视图即可查看该视图下所有数据。
1. 打开Elastic控制台选择Management->Dev Tools输入语句新建索引。
2. 选择Management->Stack Management->Data Views选择Create data view新建数据视图。
3. 输入name、index pattern刚才新建的索引和Timestamp信息保存数据视图。
4. 选择Discover选择刚才的数据视图即可查看该视图下所有数据。

View File

@@ -85,7 +85,7 @@ Installation directory: /opt/node-v20.10.0-linux-x64
```
### 1.4.4 配置Gitee
&emsp;&emsp;选择Dashboard -> Manage Jenkins -> System选择Gitee Configuration输入name和URL点击Add Credentials选择Kind的Gitee API token将Gitee中生成的token复制到指定位置。
&emsp;&emsp;选择Dashboard -> Manage Jenkins -> System选择Gitee Configuration输入name和URL点击Add Credentials选择Kind的Gitee API token将Gitee中生成的token复制到指定位置。
&emsp;&emsp;该版本新增的Gitee Token并不能在后续的项目中找到但是又无法新增username和password形式只能在项目的gitee配置中新增username和password的形式不知道是什么原因
# 二、创建工程

View File

@@ -3,28 +3,37 @@
&emsp;&emsp;[Fluent Bit](https://fluentbit.io/) 是一个开源的、轻量级、高性能的日志处理器和转发器。Fluent Bit 的核心任务是:​​**从各种来源收集日志、指标和追踪数据,进行处理和过滤,然后将其发送到一个或多个目的地。​**
#### 1.1.1 特点
1. 极致的轻量级​
&emsp;&emsp;Fluent Bit 是用 C 语言编写的,运行时内存占用极低(通常只有几百 KBCPU 使用率也非常低。这使得它成为在资源受限环境如边缘计算、IoT 设备、容器侧车)中运行的理想选择。
2. 高性能​
&emsp;&emsp;其核心架构经过高度优化,处理数据的速度极快,能够轻松应对高吞吐量的日志流。
3. 可插拔架构​
&emsp;&emsp;Fluent Bit 的功能通过“插件”来扩展。
1. 极致的轻量级​
Fluent Bit 是用 C 语言编写的,运行时内存占用极低(通常只有几百 KBCPU 使用率也非常低。这使得它成为在资源受限环境如边缘计算、IoT 设备、容器侧车)中运行的理想选择。
2. 高性能​
其核心架构经过高度优化,处理数据的速度极快,能够轻松应对高吞吐量的日志流。
3. 可插拔架构​
Fluent Bit 的功能通过“插件”来扩展。
#### 1.1.2 插件类型
1. Input输入定义数据来源例如从文件、标准输入、TCP/UDP、系统指标CPU/内存、Docker 容器、Windows 事件日志等收集数据。
2. Filter过滤器在数据传递过程中进行处理例如解析如将非结构化文本解析为 JSON、丰富如添加 Kubernetes 元数据)、修改(如重命名字段)、采样等
3. Output输出定义数据目的地例如 Elasticsearch、Splunk、Kafka、Datadog、Loki、S3、另一个 Fluentd/Fluent Bit 实例等。
1. Input输入
定义数据来源例如从文件、标准输入、TCP/UDP、系统指标CPU/内存、Docker 容器、Windows 事件日志等收集数据
2. Filter过滤器
在数据传递过程中进行处理,例如解析(如将非结构化文本解析为 JSON、丰富如添加 Kubernetes 元数据)、修改(如重命名字段)、采样等。
3. Output输出
定义数据目的地,例如 Elasticsearch、Splunk、Kafka、Datadog、Loki、S3、另一个 Fluentd/Fluent Bit 实例等。
#### 1.1.3 核心架构与数据流
1. Input输入​:一个或多个输入插件持续地从数据源收集数据。每条数据被读入后,会带上时间戳和标签等信息,被包装成一个内部的事件记录。
2. Parser解析器可选输入插件可以使用解析器将非结构化的日志数据如 Nginx 访问日志)转换为结构化的 JSON 格式
3. Filter过滤可选数据会经过一个或多个过滤器插件。这些插件可以修改、丰富或删除记录。过滤器是按顺序执行的。
4. Router路由根据每条记录的标签Tag路由器将其匹配到相应的输出插件。一个记录可以被路由到多个输出多路复用
5. Buffer缓冲在发送到输出之前数据可能会在内存或文件系统中进行缓冲。
6. Output输出最终输出插件将处理好的数据发送到配置的目的地
1. Input输入
一个或多个输入插件持续地从数据源收集数据。每条数据被读入后,会带上时间戳和标签等信息,被包装成一个内部的事件记录
2. Parser解析器
(可选)输入插件可以使用解析器,将非结构化的日志数据(如 Nginx 访问日志)转换为结构化的 JSON 格式
3. Filter过滤
(可选)数据会经过一个或多个过滤器插件。这些插件可以修改、丰富或删除记录。过滤器是按顺序执行的
4. Router路由
根据每条记录的标签Tag路由器将其匹配到相应的输出插件。一个记录可以被路由到多个输出多路复用
5. Buffer缓冲
在发送到输出之前,数据可能会在内存或文件系统中进行缓冲。
6. Output输出
最终,输出插件将处理好的数据发送到配置的目的地。
### 1.2 OpenObserve
&emsp;&emsp;[OpenObserve](https://openobserve.ai/) 是一个开源的、云原生的可观测性数据平台,旨在处理日志、指标、追踪和性能数据。它的目标是成为 Elasticsearch/Logstash/Kibana (ELK)Grafana LokiDatadog 等流行解决方案的​​更简单、更经济高效、更高性能​​的替代品。
&emsp;&emsp;[OpenObserve](https://openobserve.ai/) 是一个开源的、云原生的可观测性数据平台,旨在处理日志、指标、追踪和性能数据。它的目标是成为 Elasticsearch/Logstash/Kibana (ELK)Grafana LokiDatadog 等流行解决方案的​​更简单、更经济高效、更高性能​​的替代品。
&emsp;&emsp;OpenObserve 是一个“一体化”的平台,你可以将所有的可观测性数据(日志、指标、追踪)都发送到它这里,然后通过统一的界面进行搜索、分析和可视化。
## 二、Docker部署
@@ -94,18 +103,18 @@ volumes:
json_date_format iso8601
```
&emsp;&emsp;INPUT为日志输入源监听24422端口。
&emsp;&emsp;INPUT为日志输入源监听24422端口。
&emsp;&emsp;OUTPUT为日志输出源将日志发送到openobserve中这里使用的是容器名称URI表示openobserve接收数据的api路径default/default表示组织名称/流名称可以更改为其他的名称。_json表示以json的格式。
## 三、简单使用
### 3.1 配置数据流
&emsp;&emsp;打开控制台,选择数据流->添加数据流输入fluent bit配置文件中的流名称选择数据流类型为Logs。
&emsp;&emsp;在新添加的数据流中,点击查看,跳转到日志页面即可看到上次的日志信息。
&emsp;&emsp;如果没有日志请检查FluentBit容器日志是否有错误正确的应该会有上传成功的提示。
&emsp;&emsp;打开控制台,选择数据流->添加数据流输入fluent bit配置文件中的流名称选择数据流类型为Logs。
&emsp;&emsp;在新添加的数据流中,点击查看,跳转到日志页面即可看到上次的日志信息。
&emsp;&emsp;如果没有日志请检查FluentBit容器日志是否有错误正确的应该会有上传成功的提示。
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/a2cfe735a9245758daf6ea36a4c82f04.png '配置数据流.png')
### 3.2 配置邮件告警
&emsp;&emsp;首先需要配置模板和邮箱地址。
&emsp;&emsp;首先需要配置模板和邮箱地址。
&emsp;&emsp;修改docker-compose.yml文件添加邮件服务器
```yml
openobserve:
@@ -132,9 +141,9 @@ openobserve:
- devops
```
&emsp;&emsp;这里配置587端口会有问题使用了25端口并且去掉了安全认证。
&emsp;&emsp;注意配置的邮件地址需要和用户名一致。
&emsp;&emsp;打开控制台,选择右上角的设置按钮,选择模板->添加模板->Email填写标题和Body:
&emsp;&emsp;这里配置587端口会有问题使用了25端口并且去掉了安全认证。
&emsp;&emsp;注意配置的邮件地址需要和用户名一致。
&emsp;&emsp;打开控制台,选择右上角的设置按钮,选择模板->添加模板->Email填写标题和Body:
```json
[Alert: {alert_name}] - Severity: {alert_type}
```
@@ -162,14 +171,14 @@ openobserve:
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/352d69738b7325637e5716425757ac87.png '邮箱地址.png')
&emsp;&emsp;配置完模板和地址后,选择告警->添加告警例如可以选择当有level='critical'并且出现1次时触发报警并通知到指定邮箱。
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/05200db676132fd43d3346099facee1b.png '邮箱告警.png')
&emsp;&emsp;周期:监控数据的时间窗口范围,即每次检查告警条件时,回溯多长时间的监控数据。如果周期 = 5分钟每次检查时分析最近 5 分钟的日志/指标数据。
&emsp;&emsp;频率:告警规则检查的时间间隔,即每隔多久执行一次告警条件判断。
&emsp;&emsp;通知后静默:触发告警并发送通知后,系统暂时停止重复通知的时间。
&emsp;&emsp;也可以设置为实时告警,即当告警发生时就立即触发,不会有周期和频率参数设置。
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/05200db676132fd43d3346099facee1b.png '邮箱告警.png')
&emsp;&emsp;周期:监控数据的时间窗口范围,即每次检查告警条件时,回溯多长时间的监控数据。如果周期 = 5分钟每次检查时分析最近 5 分钟的日志/指标数据。
&emsp;&emsp;频率:告警规则检查的时间间隔,即每隔多久执行一次告警条件判断。
&emsp;&emsp;通知后静默:触发告警并发送通知后,系统暂时停止重复通知的时间。
&emsp;&emsp;也可以设置为实时告警,即当告警发生时就立即触发,不会有周期和频率参数设置。
### 3.3 配置WebHook
&emsp;&emsp;首先配置模板和WebHook地址。
&emsp;&emsp;首先配置模板和WebHook地址。
&emsp;&emsp;打开控制台,选择右上角的设置按钮,选择模板->添加模板->Web Hook填写Body:
```json
{
@@ -181,12 +190,12 @@ openobserve:
```
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/5c6514f9d689f55aa1bf74f75f5ea806.png 'WebHook模板.png')
&emsp;&emsp;选择地址->添加地址->WebHook选择刚才的模板并填写URL。
&emsp;&emsp;这里可以使用企业微信的群消息推送功能添加成功后会提供一个WebHook地址。也可以自定义一个WebHook接收地址。
&emsp;&emsp;配置完模板和地址后,选择告警->添加告警选择通知到刚才的WebHook地址即可。
&emsp;&emsp;选择地址->添加地址->WebHook选择刚才的模板并填写URL。
&emsp;&emsp;这里可以使用企业微信的群消息推送功能添加成功后会提供一个WebHook地址。也可以自定义一个WebHook接收地址。
&emsp;&emsp;配置完模板和地址后,选择告警->添加告警选择通知到刚才的WebHook地址即可。
### 3.4 通过API获取日志信息
&emsp;&emsp;通过http://ip:5080/swagger/index.html#/ 即可访问OpenobserveAPI接口。
&emsp;&emsp;通过http://ip:5080/swagger/index.html#/ 即可访问OpenobserveAPI接口。
&emsp;&emsp;例如要获取日志,可以使用/api/{origin_id}/_search的接口
```cmd
curl -X 'POST' \
@@ -204,11 +213,11 @@ curl -X 'POST' \
}'
```
&emsp;&emsp;注意这里要加上Basic auth认证并提供username和password。
&emsp;&emsp;注意这里要加上Basic auth认证并提供username和password。
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/d29ddbd369e32cb7c2770464e8d0a142.png 'API查看日志.png')
## 四、自定义日志告警服务
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/74d5efdb80f506395b81988258433d9d.png '日志框架.png')
&emsp;&emsp;基本原理为通过提供一个HTTP的API接口当Openobserver触发告警时调用该接口并传入当前的alert_name和timestamp然后根据timestamp和Openobserver提供的API接口去查询最近一段时间内的所有日志信息并存储到SqLite数据库中。同时也可以选择是否同步推送到企业微信和邮箱。
&emsp;&emsp;最后也需要提供一个查询历史日志告警信息的接口,并可视化显示,方便运维和开发查看。
&emsp;&emsp;基本原理为通过提供一个HTTP的API接口当Openobserver触发告警时调用该接口并传入当前的alert_name和timestamp然后根据timestamp和Openobserver提供的API接口去查询最近一段时间内的所有日志信息并存储到SqLite数据库中。同时也可以选择是否同步推送到企业微信和邮箱。
&emsp;&emsp;最后也需要提供一个查询历史日志告警信息的接口,并可视化显示,方便运维和开发查看。
&emsp;&emsp;自定义服务参考代码:[log-alert-service](https://cxxgit.iepose.cn/Cxx0822/log-alert-service)

View File

@@ -0,0 +1,72 @@
# 一、原生镜像
&emsp;&emsp;传统 Java 应用基于 JVM 运行,需要加载完整的类库和 JVM 运行时环境,导致启动时间长、内存占用高。而**原生镜像技术通过提前编译AOT将 Java 应用直接编译为本地机器码,无需 JVM 即可运行**,具有以下核心优势:
1. 启动速度极快:毫秒级启动,相比传统 JVM 应用提升 10-100 倍、
2. 内存占用低:仅包含应用运行所需的最小资源集合
3. 部署体积小:通常为传统 JAR 包的 1/10 左右
4. 适合云原生场景:尤其适合 Kubernetes 等容器化环境
# 二、安装使用
## 2.1 基础软件
1. GraalVM类似于JAVA JDK下载[官网](https://www.graalvm.org/downloads/),下载完成后需要配置环境变量
2. native-image安装cmd运行gu install native-image
3. Visual Studio Community2022 选择使用C++的桌面开发
## 2.2 结合Springboot3 项目
1. IDEA创建Springboot3项目
2. Project Structure选择GraalVM JDK
3. 配置pom打包
```xml
<plugins>
<!-- 将项目打包成可执行的 JAR/WAR 文件 -->
<!-- repackage目标会替换 Maven 默认的打包行为 -->
<!-- 使 JAR/WAR 文件包含所有依赖项,成为一个独立运行的应用 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
<!-- 执行 AOT 编译 -->
<goal>process-aot</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- GraalVM Native Maven 插件 -->
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.10.2</version>
<configuration>
<buildArgs combine.children="append">
<!-- 支持 http 协议 -->
<buildArg>--enable-url-protocols=http</buildArg>
<!-- 用于解决数据库中文乱码问题 -->
<buildArg>-H:+AddAllCharsets</buildArg>
</buildArgs>
</configuration>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
```
4. 选择Maven->Lifecycle->clean和package或者输入命令mvn -Pnative native:compile
5. 在target目录下可以看到生成的.exe文件打开运行即可。
## 三、注意事项
&emsp;&emsp;实际体验下来发现主要存在以下几个问题
1. 编译打包的速度非常慢
2. 目前有些第三方库并不支持Mybatis已支持Mybatis Plus暂不支持
# 四、参考资料
1. [使用 Spring Boot 和 GraalVM 构建原生镜像](https://springdoc.cn/spring-native-intro/)
2. [GraalVM 原生镜像支持](https://docs.springframework.org.cn/spring-boot/reference/native-image/introducing-graalvm-native-images.html)

View File

@@ -0,0 +1,60 @@
## 一、Bean概念
### 1.1 定义
&emsp;&emsp;Spring bean是Spring框架在运行时管理的对象Bean是一个由Spring IoC容器实例化、组装和管理的对象。
&emsp;&emsp;一个 Bean 可以是任何普通的 Java 对象,例如 POJO、Service、Respository、Controller 等等。将一个类声明为 Bean 的方式可以是在类级别上使用 `@Component` 注解或其派生注解(`@Service``@Repository``@Controller`等),也可以是通过配置文件进行显式的声明。
### 1.2 实例化
&emsp;&emsp;Spring 容器负责实例化 Bean。当应用程序启动时Spring 容器会根据配置信息或注解扫描的结果,找到并实例化所有被标记为 Bean 的类,并将它们加入容器中。实例化的过程由 Spring 的 IoC 容器负责。
### 1.3 管理
&emsp;&emsp;一旦 Bean 被实例化Spring 容器将负责管理 Bean 的生命周期和依赖关系。它会根据配置文件或注解的信息,自动解决 Bean 之间的依赖关系确保在需要的时候正确的注入依赖。Spring 容器还会负责销毁不再需要的 Bean。
### 1.4 依赖注入
&emsp;&emsp;依赖注入是 Spring 框架的一个重要特性,它允许通过自动或显式配置的方式将 Bean 的依赖项注入到其它 Bean 中。依赖注入可以通过构造函数注入、Setter 方法注入或字段注入的方式实现,其中最常见的是使用 `@Autowired`注解进行注入。
### 1.5 作用域
&emsp;&emsp;Spring 框架提供了多种作用域scope来管理 Bean 的生命周期。常见的作用域包括单例Singleton、原型Prototype、会话Session、请求Request等。默认情况下Bean 是单例的,即每个容器中只存在一个实例。但可以根据需要配置其它作用域。
### 1.6 自动装配
&emsp;&emsp;Spring Boot 支持自动装配Auto - wiring它能够根据类型或名称自动解析和注入依赖关系。通过在需要注入的字段、构造函数或 Setter 方法上使用 `@Autowired` 注解Spring 容器会自动查找并注入对应的 Bean。
## 二、Bean生命周期
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/b30628a6b1fa3cfd250cc5efff612a68.jpg 'Bean生命周期.jpg')
### 2.1 实例化
&emsp;&emsp;指创建对象的过程,也就是说,当 Spring 容器启动时,它会扫描所有的配置文件,读取 Bean 定义,然后根据定义创建 Bean 对象的实例。这个过程中Spring 容器会使用反射机制来调用 Bean 的构造函数来实例化对象。
### 2.2 属性赋值
&emsp;&emsp;对 Bean 的实例化对象进行属性填充;
### 2.3 初始化
&emsp;&emsp;指 Bean 对象被创建后Spring 容器会根据配置文件中的定义来对其进行一系列的属性设置、依赖注入等操作使其可以正常运行。这个过程中Spring 容器会调用 Bean 的一些特定方法,如 set 方法、init-method 方法等来完成对象的初始化。
### 2.4 销毁
&emsp;&emsp;执行一些预定义的销毁方法;
## 三、Bean注解——@Component
### 基本概念
&emsp;&emsp;@Component 是 Spring 框架中的一个注解,用于将类标记为 Spring 管理的组件。当一个类被 @Component 注解修饰时Spring 会在启动时自动扫描并将该类实例化为 Bean放入应用程序的上下文ApplicationContext以便在其他地方可以通过依赖注入使用。
### 主要作用
1. 组件扫描Spring 通过组件扫描机制自动发现并注册被 @Component 注解的类。
2. Bean 定义:被注解的类会被视为 Spring Bean由 Spring 容器管理其生命周期。
3. 依赖注入:其他组件可以通过注解(如 @Autowired)注入这些 Bean。
### 其他衍生注解
| **注解** | **所属层** | **继承关系** | **适用场景** |
|------------------|--------------------|-------------------|----------------------------------------------------------------------------|
| **@Component** | 通用组件 | 基础注解 | 不适合特定层的组件(如工具类、配置类) |
| **@Repository** | 数据访问层 (DAO) | 继承 @Component | 数据库操作类、ORM 映射类(如 JPA Repository |
| **@Service** | 服务层 (Service) | 继承 @Component | 业务逻辑处理(如订单处理、用户认证) |
| **@Controller** | 控制层 (MVC/Web) | 继承 @Component | 处理 HTTP 请求的控制器(如 REST API、Web 页面控制器) |
| **@RestController** | 控制层 (REST API) | 继承 @Controller | RESTful API 接口开发 |
| **注解** | **功能描述** | **Spring 自动处理特性** |
|------------------|-----------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|
| **@Component** | 标记类为 Spring Bean通过组件扫描自动注册。<br>无特定语义,是其他组件注解的基础。 | - Bean 生命周期管理<br>- 依赖注入支持 |
| **@Repository** | 标记数据访问组件(如数据库操作类),<br>自动处理数据访问异常。 | - 异常转换<br>- DAO 组件扫描 |
| **@Service** | 标记业务逻辑组件,负责处理业务流程、事务管理等。<br>提供语义化,明确该类在架构中的角色。 | - 与 @Transactional 注解默认集成<br>- IDE 识别为服务层组件 |
| **@Controller** | 标记 Web 控制器,处理 HTTP 请求。<br>与 Spring MVC 框架紧密集成。 | - 请求映射自动注册<br>- 支持 @RequestMapping@GetMapping 等注解<br>- 视图解析(返回视图名称或 JSON |
| **@RestController** | @Controller + @ResponseBody 的组合,专门用于 RESTful API直接返回 JSON/XML 等数据。 | - 所有方法默认返回 JSON/XML无需每个方法添加 @ResponseBody |

View File

@@ -0,0 +1,105 @@
# 一、简介
&emsp;&emsp;Spring Boot Starter是一组预定义的依赖项集合旨在简化Maven或Gradle等构建工具中的依赖管理。每个Starter都包含了实现特定功能所需的库和组件以及相应的配置文件。开发者只需在项目中引入相应的Starter依赖即可快速搭建起具备该功能的项目骨架。
&emsp;&emsp;Starter=依赖+自动配置+配置文件
# 二、实现原理
## 2.1 传统实现
&emsp;&emsp;例如引入Spring中的jpa则需要以下步骤
1. 在Maven中引入数据库依赖
2. 在Maven中引入jpa依赖
3. 在配置文件中配置属性
4. 调试程序
&emsp;&emsp;每次新建项目都需要重复此流程,操作繁琐。
## 2.2 自定义Starter实现
1. 新建Maven项目在pom.xml文件中定义需要的依赖项。
2. 创建自动配置类 AutoConfigurationTest添加@configuration注解使其能够被SpringBoot自动扫描到。
3. 添加自动装配机制在src/main/resources/META-INF文件夹下创建spring.factories文件添加以下配置
```bash
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.springbootstartercustom.AutoConfigurationTest
```
&emsp;&emsp;这里文件夹和文件名一定要正确因此SpringFactoriesLoader中就是这么定义的。
4. 在配置文件中自定义属性(可选)。
5. 安装打包到maven仓库中。
6. 其他项目通过pom.xml文件引入该starter。
## 2.3 Starter实现原理
&emsp;&emsp;加载依赖->扫描自动配置类->加载配置文件
# 三、高级特性
## 3.1 可插拔Starter
&emsp;&emsp;所谓可插拔就是可以自行决定是否需要加载该starter的功能。例如可以通过注解的方式决定是否加载。
1. 定义注解
```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableAutoConfigTest {
}
```
2. 在自动配置类 AutoConfiguration中增加条件注解
```java
@Configuration
@ConditionalOnBean(annotation = EnableAutoConfigTest.class)
public class AutoConfigurationTest {
}
```
3. 在相应位置添加@EnableAutoConfigTest注解该stater才会生效
## 3.2 自定义配置文件
&emsp;&emsp;所谓自定义配置文件就是可以在引入stater后可以通过修改配置文件覆盖原来的配置属性从而灵活配置stater功能。
1. 引用spring-boot-configuration-processor
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
```
2. 定义Properties配置类
```java
@ConfigurationProperties(prefix = "test")
public class TestProperties {
private String name = "test";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
```
3. 在自动配置类 AutoConfigurationTest中引用
```java
@Configuration(proxyBeanMethods = false)
@Import({TestProperties.class})
public class AutoConfigurationTest {
@Resource
private TestProperties testProperties;
}
```
4. 在引入的stater工程中修改配置文件
```yaml
test:
name: test1
```

View File

@@ -0,0 +1,89 @@
# 一、基础概念
&emsp;&emsp;Spring MVCSpring Model-View-Controller是 Spring Framework 中的一部分,它是一个基于 **<font style="color:#DF2A3F;">请求驱动</font>** 的 Web 框架,主要用于构建 Web 应用程序,并且遵循 MVC模型-视图-控制器) 设计模式。它将 Web 应用的业务逻辑、用户界面和请求处理分离,使得代码更加模块化和可维护。
# 二、具体流程
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/04fb16a3e32f247bbf0bb7e176de8444.png '具体流程.png')
1. **客户端发起HTTP请求**
&emsp;&emsp;客户端浏览器、API 客户端等)向服务器发送 HTTP 请求。请求包含了目标 URL、HTTP 方法GET、POST 等)以及可能的请求参数。
前端通过发送 HTTP 请求(通常是 AJAX 请求)到后端的 API 端点,并期望获得响应(通常是 JSON 数据)。
2. **请求到达 DispatcherServlet**
&emsp;&emsp;所有 HTTP 请求都会先到达 Spring MVC 的核心组件:**<font style="color:#DF2A3F;">DispatcherServlet</font>**。它是前端控制器负责将请求分发给正确的处理器。DispatcherServlet 是所有请求的入口Spring Boot 会自动配置它。
&emsp;&emsp;DispatcherServlet 接受所有的 HTTP 请求,根据请求 URL 和 HTTP 方法,定位匹配的控制器方法,将请求交给相应的 Handler Mapping 来匹配控制器。在请求处理前后,执行 拦截器(可选)。最终通过 View Resolver 返回一个合适的视图(对于 RESTful API通常返回 JSON 格式的数据)。
3. **查找处理器Handler Mapping**
&emsp;&emsp;DispatcherServlet 根据请求的 URL 和 HTTP 方法,查找与之匹配的处理器方法(通常是一个控制器中的方法)。这是由 HandlerMapping 完成的。Spring MVC 提供了多种方式来映射 URL 到控制器方法,例如 @RequestMapping@GetMapping 等注解。
&emsp;&emsp;@RestController:该注解标记的类会返回 JSON 数据。
&emsp;&emsp;@RequestMapping:用于映射 URL 路径和 HTTP 方法。
&emsp;&emsp;@GetMapping@PostMapping 等:简化版本的 @RequestMapping
4. **执行拦截器Interceptor**
&emsp;&emsp;在请求到达控制器之前DispatcherServlet 会依次执行配置的拦截器。拦截器主要用于日志记录、安全检查、请求参数的预处理等。
&emsp;&emsp;preHandle请求进入控制器方法之前。
&emsp;&emsp;postHandle请求完成后视图渲染之前。
&emsp;&emsp;afterCompletion视图渲染完成后。
&emsp;&emsp;**<font style="color:#DF2A3F;">如果拦截器的 preHandle 方法返回 false请求会被拦截后续的处理不会继续进行。</font>**
5. **控制器方法处理请求Handler Execution**
&emsp;&emsp;DispatcherServlet 调用合适的控制器方法来处理请求。控制器方法负责处理请求的业务逻辑,并返回一个 Java 对象,这个对象会被自动转换成 JSON 格式返回给客户端。
&emsp;&emsp;Spring 通过 @ResponseBody 注解(或 @RestController)实现将 Java 对象自动转换为 JSON 格式。默认情况下Spring 使用 Jackson 来将 Java 对象序列化成 JSON 数据。
&emsp;&emsp;@RestController:将控制器标记为返回 JSON 数据的 RESTful 控制器。
&emsp;&emsp;@ResponseBody:标记方法返回的数据需要直接作为响应体返回,而不是解析为视图。
&emsp;&emsp;@RequestParam:用于获取单个请求参数。
&emsp;&emsp;@PathVariable:用于从 URL 中提取变量(如 /users/{id} 中的 {id})。
6. **响应返回客户端**
&emsp;&emsp;DispatcherServlet 将处理完的结果JSON 数据)返回给客户端。响应数据通常会包含:
&emsp;&emsp;**状态码**:如 200 OK成功、201 Created资源创建成功、404 Not Found资源未找到、500 Internal Server Error服务器错误等。
&emsp;&emsp;**响应头**:通常包括 Content-Type: application/json表示响应体是 JSON 格式。
&emsp;&emsp;**响应体**:包含返回的数据,通常是一个 JSON 格式的对象或数组。
以上只适用于基于RESTful API 前后端分离的架构。
# 三、完整流程
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/cad3b69253771b02b07d94f7d01c0e4f.png '完整流程.png')
&emsp;&emsp;在SpringMVC前面还有Tomcat容器层。客户端发起请求后先经过Tomcat容器根据请求的 URL 确定该请求是否是静态资源请求(如 .html、.css、.js。如果是静态资源请求Tomcat 会直接返回文件,而不经过 Spring MVC 的请求处理链。对于动态请求(如请求 /user 等Tomcat 会将这些请求交给 DispatcherServlet 进行处理。
&emsp;&emsp;在进入DispatcherServlet前可以增加过滤器进行处理。过滤器不属于Spring部分。
&emsp;&emsp;Tomcat 是一个轻量级的 Web 服务器和 Servlet 容器,支持 Java Servlet 和 JSP 规范,能够处理 HTTP 请求并生成动态 Web 内容。
&emsp;&emsp;Servlet 是 Java 编写的服务器端程序,能够接收 HTTP 请求并生成响应,它是动态 Web 内容生成的核心技术。
&emsp;&emsp;Tomcat 管理 Servlet 的生命周期,并负责处理客户端的请求和返回响应。
# 四、重要知识点
## 4.1 拦截器和过滤器区别
1. 拦截器不依赖与servlet容器过滤器依赖与servlet容器。
2. 拦截器只能对action请求(DispatcherServlet 映射的请求)起作用,而过滤器则可以对几乎所有的请求起作用。
3. 拦截器可以访问容器中的Bean(DI)而过滤器不能访问基于spring注册的过滤器也可以访问容器中的bean
4. 执行顺序:过滤器->拦截器
## 4.2 拦截器执行顺序
&emsp;&emsp;HandlerInterceptor 拦截器执行顺序:
1. preHandle():请求处理前 按注册顺序依次执行。
2. Controller请求处理 请求到达Controller并被处理。
3. postHandle():请求处理后,视图渲染前 按注册顺序逆序执行。
4. afterCompletion():视图渲染后 按注册顺序逆序执行。
## 4.3 请求和响应体拦截器
&emsp;&emsp;RequestBodyAdvice, ResponseBodyAdvice主要发生在Controller执行前后
1. preHandle():请求处理前
2. beforeBodyRead请求体反序列化前
3. @RequestBodyController方法参数绑定
4. Controller请求处理
5. beforeBodyWrite():响应体序列化之前
6. postHandle():请求处理后
7. 视图渲染
8. afterCompletion():请求结束
## 4.4 @Controller 和 @RestController区别
&emsp;&emsp;@Controller:用于定义一个 Spring MVC 控制器类。它通常用于返回视图(如 JSP 或 Thymeleaf
&emsp;&emsp;@RestController:是 @Controller@ResponseBody 的组合,用于开发 RESTful API。它将方法的返回值直接写入 HTTP 响应体中,通常返回 JSON 或 XML 格式的数据。
## 4.5 异常处理
&emsp;&emsp;@ExceptionHandler:用于控制器类或方法中处理特定异常。它会捕获指定的异常并返回相应的响应(如错误页面、错误消息等)。
&emsp;&emsp;@ControllerAdvice:是一个全局异常处理器,用于捕获应用中所有控制器抛出的异常。

View File

@@ -7,3 +7,7 @@ title: SpringBoot
- [Spring IOC简介](/SpringBoot/SpringIOC)
- [SpringBoot启动流程](/SpringBoot/SpringBoot-Start-Process)
- [Spring MVC简介](/SpringBoot/SpringMVC)
- [SpringBoot Starter原理](/SpringBoot/SpringBootStarter)
- [SpringBoot Bean简介](/SpringBoot/SpringBootBean)
- [SpringBoot3原生镜像](/SpringBoot/SpringBoot3-GraalVM)

View File

@@ -0,0 +1,57 @@
# 一、简介
&emsp;&emsp;Flyway 是一个开源的数据库版本控制工具,它极大地简化了数据库的迁移和版本管理。它的核心思想是​​像**用 Git 管理代码一样来管理数据库的结构**​​。
# 二、原理
&emsp;&emsp;Flyway 通过在数据库中创建一个名为 flyway_schema_history的特殊表来工作
| 列名 | 含义 |
| - | - |
| installed_rank | 执行顺序 |
| version | 脚本的版本号 |
| description | 脚本的描述 |
| type | 脚本类型(通常是 SQL |
| script | 脚本文件名 |
| checksum | 脚本文件的校验和(用于检测篡改) |
| installed_by | 执行人 |
| installed_on | 执行时间 |
| execution_time | 执行耗时(毫秒) |
| success | 是否成功 |
&emsp;&emsp;工作流程:
1. 应用启动时Flyway 会检查配置的数据库路径。
2. 检查目标数据库中的 flyway_schema_history表。
3. 将数据库路径下的迁移脚本与 flyway_schema_history表中的记录进行对比。
4. 按照版本号​​顺序​​执行那些尚未执行的迁移脚本。
5. 执行成功后,将记录插入 flyway_schema_history表。
## 2.1 校验和计算
&emsp;&emsp;Flyway 使用 CRC32 算法​​ 计算 SQL 脚本文件的校验和Checksum。Javs使用32位有符号整数存储因此有时会得到负数。可以通过`mvn flyway:info`查看每个脚本的校验和。
# 三、与SpringBoot集成
## 3.1 添加依赖
```xml
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
```
## 3.2 配置数据源
```yml
spring:
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true # 如果数据库非空,且无 flyway_schema_history 表,则先创建基线版本
```
&emsp;&emsp;`baseline-on-migrate: true`当数据库已经存在数据但是没有flyway_schema_history表时Flyway会插入一条基线数据并标记为1.0版本则后续的迁移脚本只会执行比1.0版本更高的数据库文件。
&emsp;&emsp;**如果设置为false如果数据库已经存在数据时Flyway会报错。**
&emsp;&emsp;因此需要避免这种情况的存在,在**初始发布应用时,要保证数据库为空**。
## 3.3 创建数据库脚本
&emsp;&emsp;在项目的资源目录 src/main/resources下创建文件夹 db/migration。
&emsp;&emsp;Flyway 的 SQL 脚本文件名有严格的命名规则:`V<Version>__<Description>.sql`
&emsp;&emsp;例如V1.0.0_001__20251027.sql表示v1.0.0版本的第一个sql日期为2025年10月27日。
&emsp;&emsp;**创建了迁移脚本,一旦应用,就不可修改,否则会导致校验错误。如果确实要修改,请再创建一个脚本。**
## 3.4 启动程序
&emsp;&emsp;启动程序后。Flyway会自动在数据库中创建flyway_schema_history表然后扫描db/migration目录下的所有脚本按顺序执行sql文件并记录到flyway_schema_history表中。

453
docs/Web-Backend/MyBatis.md Normal file
View File

@@ -0,0 +1,453 @@
# 一、简介
&emsp;&emsp;[MyBatis](https://mybatis.org/mybatis-3/)是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJOPlain Old Java Objects普通老式 Java 对象)为数据库中的记录。
# 二、安装
### 2.1 引入依赖
&emsp;&emsp;`pom.xml`文件中,引入依赖:
```xml
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
```
&emsp;&emsp;可以在Github中查看[MyBatis](https://github.com/mybatis/mybatis-3)最新版本号。
### 2.2 配置文件
&emsp;&emsp;`resource`文件夹中新建mybatis-config.xml文件和mapper->BlogMapper.xml映射文件
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 环境配置 -->
<environments default="development">
<!-- 环境名称 -->
<environment id="development">
<!-- 事务管理器配置 -->
<transactionManager type="JDBC"/>
<!-- 数据源配置 -->
<dataSource type="POOLED">
<!-- JDBC驱动名称 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- 数据库地址 -->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_learn?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT"/>
<!-- 数据库用户名 -->
<property name="username" value="root"/>
<!-- 数据库密码-->
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 映射器 -->
<mappers>
<!-- mapper文件 -->
<mapper resource="mapper/BlogMapper.xml"/>
</mappers>
</configuration>
```
&emsp;&emsp;如果使用MySql数据库需要增加MySql驱动依赖
```xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
```
### 2.3 定义映射语句
&emsp;&emsp;新建dao->BlogDao.java
```java
public interface BlogDao {
Blog selectBlog(@Param("id") Integer id);
}
```
&emsp;&emsp;BlogMapper.xml
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatislearn.dao.BlogDao">
<select id="selectBlog" resultType="com.example.mybatislearn.entity.Blog">
select * from Blog where author_id = #{id}
</select>
</mapper>
```
### 2.4 执行SqlSession
```java
// 配置文件路径
String resource = "mybatis-config.xml";
try {
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构建SqlSession工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取SqlSession
SqlSession session = sqlSessionFactory.openSession();
// 获取映射文件
BlogDao blogDao = session.getMapper(BlogDao.class);
// 执行已映射的SQL语句
Blog blog = blogDao.selectBlog(101);
System.out.println(blog);
// 关闭SqlSession
session.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
```
&emsp;&emsp;每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。
&emsp;&emsp;SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。
&emsp;&emsp;而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
### 2.5 作用域和生命周期
1. SqlSessionFactoryBuilder
&emsp;&emsp;这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
2. SqlSessionFactory
&emsp;&emsp;SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
3. SqlSession
&emsp;&emsp;每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession返回一个响应后就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。
# 三、注入SpringBoot框架
## 3.1 引入依赖
&emsp;&emsp;将之前MyBatis的依赖替换成MyBatis的SpringBoot Starter
```xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
```
## 3.2 配置文件
&emsp;&emsp;在Resource文件夹下新建application.yml
```yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_learn?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
username: root
password: 123456
mybatis:
# mapper文件路径
mapper-locations: classpath*:mapper/*Mapper.xml
configuration:
# 开启日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
```
&emsp;&emsp;这里的url的写法和XML文件中的写法不一致。
# 3.3 定义映射语句
&emsp;&emsp;在原映射接口文件BlogDao.java中添加@Mapper注解
## 3.4 实现原理
&emsp;&emsp;引入mybatis-spring-boot-starter模块之后其可以
1. **自动检测DataSource**
2. **使用SqlSessionFactoryBean注册SqlSessionFactory 实例并设置DataSource数据源**
3. **基于SqlSessionFactory自动注册SqlSessionTemplate实例**
4. **自动扫描@Mapper注解类并通过SqlSessionTemplate注册到Spring Context中**
&emsp;&emsp;每次执行@Mapper映射文件中的接口时都会自动开启一个SqlSession并在执行结束时关闭。
## 3.5 执行映射语句
```java
Blog blog = blogDao.selectBlog(101);
System.out.println(blog);
```
&emsp;&emsp;相较之前的写法,节省了大量的配置工作。
# 四、高级特性
## 4.1 动态参数
```xml
#{}是参数占位符的标记它可以防止SQL注入当使用#{}时MyBatis会自动处理参数的数据类型
如果参数是字符串它会给传入的值加上引号这样可以有效地防止SQL注入攻击。
${}则是直接将参数值嵌入SQL语句中。当使用${}时传入的参数会直接显示在SQL中
MyBatis不会对参数进行任何类型转换或加引号处理。一般用在动态表名、列名或数据库名称中。
```
## 4.2 SQL片段
&emsp;&emsp;可以用来定义可重复的SQL代码片段
```xml
<sql id="userColumns">
id, username, email, phone
</sql>
<select id="findAllUsers" resultType="User">
SELECT <include refid="userColumns" /> FROM users
</select>
```
## 4.3 批量操作
&emsp;&emsp;推荐使用集合方式批量操作:
```java
@Mapper
public interface UserMapper {
Integer insertUsers(@Param("list") List<User> userList);
}
```
```xml
<insert id="insertUsers">
INSERT INTO user (username, password)
VALUES
<foreach collection ="list" item="item" separator =",">
(#{item.username}, #{item.password})
</foreach>
</insert>
```
## 4.4 结果映射
&emsp;&emsp;复杂结果缓存
```xml
<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
<!-- 一般不需要 -->
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<!-- 复杂对象 1对1 -->
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<!-- 列表 1对多-->
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
```
&emsp;&emsp;其中`<collection>`也可以使用嵌套查询:
```xml
<collection property="posts" ofType="Post" select="queryPost"/>
<resultMap id="postResultMap" type="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</resultMap>
<select id="queryPost" resultMap="postResultMap">
</select>
```
&emsp;&emsp;如果需要传递参数,可以在`<collection>`添加`column`属性:
```xml
<!-- 单个参数 -->
<collection property="posts" column="name" ofType="Post" select="queryPost"/>
<!-- 多个参数 -->
<collection property="posts" column="{param1=param_1, param2=param_2}" ofType="Post" select="queryPost"/>
```
&emsp;&emsp;注:建立在非列表数据时使用嵌套查询,否则每查到一个数据都会进行一次子查询操作。
## 4.5 一二级缓存
&emsp;&emsp;默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:<cache/>
+ 映射语句文件中的所有 select 语句的结果将会被缓存。
+ 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
+ 一级缓存和二级缓存区别在于一级缓存只针对一次SqlSession二级缓存针对全局范围。
## 4.6 动态SQL
+ if :是/否
```xml
<select id="findActiveBlogWithTitleLike" resultType="Blog">
SELECT * FROM BLOG
WHERE state = 'ACTIVE'
<if test="title != null">
AND title like #{title}
</if>
</select>
```
+ choose、when、otherwise选择其中一个
```xml
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
```
+ where、set解决SQL语法问题
```xml
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
</where>
</select>
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
</set>
where id=#{id}
</update>
```
&emsp;&emsp;where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”where 元素也会将它们去除。
# 五、自定义类型处理器
&emsp;&emsp;MyBatis 在预处理语句PreparedStatement中设置参数时会从 Java 类型javaType转换为 JDBC 类型jdbcType而从结果集中取出值时会将 JDBC 类型转换为 Java 类型。这个转换工作就是由 TypeHandler来完成的。
&emsp;&emsp;需要创建一个类来实现 org.apache.ibatis.type.TypeHandler接口或者继承 org.apache.ibatis.type.BaseTypeHandler类实现自定义类型处理器。
&emsp;&emsp;需要实现的方法:
```java
/**
* 将Java对象设置到PreparedStatement中Java类型 → JDBC类型
* @param ps PreparedStatement对象
* @param i 参数位置从1开始
* @param parameter 要设置的Java对象非空
* @param jdbcType JDBC类型
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
// 实现转换逻辑T → JDBC类型
}
/**
* 根据列名从ResultSet中获取值JDBC类型 → Java类型
* @param rs ResultSet对象
* @param columnName 列名
* @return 转换后的Java对象
*/
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 实现转换逻辑JDBC类型 → T
}
/**
* 根据列索引从ResultSet中获取值
* @param rs ResultSet对象
* @param columnIndex 列索引从1开始
* @return 转换后的Java对象
*/
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
// 实现转换逻辑JDBC类型 → T
}
/**
* 从CallableStatement中获取值用于存储过程
* @param cs CallableStatement对象
* @param columnIndex 列索引
* @return 转换后的Java对象
*/
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
// 实现转换逻辑JDBC类型 → T
}
```
&emsp;&emsp;例如自定义一个处理 MEDIUMBLOB 字段与 Base64 字符串的转换:
```java
/**
* 处理 MEDIUMBLOB 字段与 Base64 字符串的转换
*/
@MappedJdbcTypes(JdbcType.BLOB)
@MappedTypes(String.class)
public class BlobToBase64TypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
// Base64字符串 -> 数据库的byte[]
if (parameter != null && !parameter.trim().isEmpty()) {
byte[] bytes = Base64.getDecoder().decode(parameter);
ps.setBytes(i, bytes);
} else {
ps.setBytes(i, null);
}
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 数据库的byte[] -> Base64字符串
byte[] bytes = rs.getBytes(columnName);
return bytes != null ? Base64.getEncoder().encodeToString(bytes) : null;
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
byte[] bytes = rs.getBytes(columnIndex);
return bytes != null ? Base64.getEncoder().encodeToString(bytes) : null;
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
byte[] bytes = cs.getBytes(columnIndex);
return bytes != null ? Base64.getEncoder().encodeToString(bytes) : null;
}
}
```

View File

@@ -2,39 +2,39 @@
## 一、基础知识
### 1.1 数据类型
#### 1.1.1 汇总
|类型|存储空间|范围|适用场景|备注|
| :-------------------------------------------------------------------------------------------------: | :-----------------: | :--------------------------: | :-----------------------: | :------------------------------------: |
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">TINYINT</span>|1字节|-128 \~ 127|状态码、年龄、布尔值||
|类型|存储空间|范围|适用场景|
| :-------------------------------------------------------------------------------------------------: | :-----------------: | :--------------------------: | :-----------------------: |
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">TINYINT</span>|1字节|-128 \~ 127|状态码、年龄、布尔值|
|SMALLINT|2字节|-32,768 \~ 32,767|小范围计数、年份||
|MEDIUMINT|3字节|-8,388,608 \~ 8,388,607|中型ID、访问量统计||
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">INT/INTEGER</span>|4字节|-2\^31 \~ 2\^31-1|用户ID、订单号常用||
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">BIGINT</span>|8字节|-2\^63 \~ 2\^63-1|分布式ID、大数据量计数||
|MEDIUMINT|3字节|-8,388,608 \~ 8,388,607|中型ID、访问量统计|
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">INT/INTEGER</span>|4字节|-2\^31 \~ 2\^31-1|用户ID、订单号常用|
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">BIGINT</span>|8字节|-2\^63 \~ 2\^63-1|分布式ID、大数据量计数|
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">DECIMAL(M,D)</span>|变长M+2字节||金融金额、精确计算|精确小数M\=总位数D\=小数位|
|FLOAT|4字节||科学测量、非精确计算|单精度浮点约6-7位有效数字|
|DOUBLE|8字节||地理坐标、高精度计算|双精度浮点约15位有效数字|
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">CHAR(M)</span>|255字符||固定长度编码、MD5哈希||
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">VARCHAR(M)</span>|65,535字节||用户名、地址等变长数据||
|TINYTEXT|255字节||短标题、简介||
|TEXT|65,535字节||文章内容、评论||
|MEDIUMTEXT|16MB (2\^24-1)||博客文章、产品描述||
|LONGTEXT|4GB (2\^32-1)||电子书、大型文档||
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">DATE</span>|||纯日期||
|TIME(fsp)|||可指定微秒精度(0-6)||
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">DATETIME(fsp)</span>|||高精度时间记录||
|TIMESTAMP(fsp)|||自动时区转换4字节存储||
|FLOAT|4字节||科学测量、非精确计算|
|DOUBLE|8字节||地理坐标、高精度计算|
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">CHAR(M)</span>|255字符||固定长度编码、MD5哈希|
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">VARCHAR(M)</span>|65,535字节||用户名、地址等变长数据|
|TINYTEXT|255字节||短标题、简介|
|TEXT|65,535字节||文章内容、评论|
|MEDIUMTEXT|16MB (2\^24-1)||博客文章、产品描述|
|LONGTEXT|4GB (2\^32-1)||电子书、大型文档|
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">DATE</span>|||纯日期|
|TIME(fsp)|||可指定微秒精度(0-6)|
|<span data-type="text" style="background-color: var(--b3-card-error-background); color: var(--b3-card-error-color);">DATETIME(fsp)</span>|||高精度时间记录|
|TIMESTAMP(fsp)|||自动时区转换4字节存储|
|ENUM|||性别、状态等有限选项|`gender ENUM('M','F','O')`|
|SET|||用户兴趣、文章标签|`tags SET('red','green','blue')`|
|TINYBLOB|255字节||微小二进制对象||
|BLOB|65KB||标准二进制对象||
|MEDIUMBLOB|16MB||中等二进制对象||
|LONGBLOB|4GB||超大二进制对象||
|TINYBLOB|255字节||微小二进制对象|
|BLOB|65KB||标准二进制对象|
|MEDIUMBLOB|16MB||中等二进制对象|
|LONGBLOB|4GB||超大二进制对象|
#### 1.1.2 法则
&emsp;&emsp;整数选择优先INT大数量用BIGINT布尔值用TINYINT(1)或BIT(1)
&emsp;&emsp;小数选择金融金额必须用DECIMAL非精确测量可用FLOAT/DOUBLE
&emsp;&emsp;字符串选择定长编码用CHAR变长文本用VARCHAR大文本用TEXT系列
&emsp;&emsp;时间选择日期用DATE精确时间用DATETIME自动更新用TIMESTAMP
&emsp;&emsp;特殊场景多选项用SET结构化数据用JSON
&emsp;&emsp;**整数选择**优先INT大数量用BIGINT布尔值用TINYINT(1)或BIT(1)
&emsp;&emsp;**小数选择**金融金额必须用DECIMAL非精确测量可用FLOAT/DOUBLE
&emsp;&emsp;**字符串选择**定长编码用CHAR变长文本用VARCHAR大文本用TEXT系列
&emsp;&emsp;**时间选择**日期用DATE精确时间用DATETIME自动更新用TIMESTAMP
&emsp;&emsp;**特殊场景**多选项用SET结构化数据用JSON
### 1.2 常用函数
#### 1.2.1 字符串函数
@@ -105,7 +105,7 @@ FROM employees;
```
## 二、索引
&emsp;&emsp;索引是对数据库表中的一列或多列值进行**排序**的一种结构,使用索引可以快速访问数据库表中的特定信息。
&emsp;&emsp;索引是对数据库表中的一列或多列值进行**排序**的一种结构,使用索引可以快速访问数据库表中的特定信息。
&emsp;&emsp;**索引相当于图书上的目录,可以根据目录上的页码快速找到所需的内容,提高性能(查询速度)**
### 2.1 优缺点
@@ -133,7 +133,7 @@ FROM employees;
### 2.3 数据结构
&emsp;&emsp;在 MySQL 中MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构。
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/91d2e40a1dae728c4061d1e1845e0ae3.png 'MySQL索引.png')
&emsp;&emsp;例如要查找id=75的用户`SELECT * FROM users WHERE id = 75`
&emsp;&emsp;例如要查找id=75的用户`SELECT * FROM users WHERE id = 75`
&emsp;&emsp;查找步骤:
1. 从根节点开始75在50~100之间走中间分支
2. 到达叶子节点找到id=75的数据位置
@@ -148,8 +148,8 @@ FROM employees;
|只在叶子存数据|内部节点小|更多索引数据放入内存|
### 2.4 索引类型
1. 主键索引
&emsp;&emsp;主键索引是最特殊的索引。
1. 主键索引
主键索引是最特殊的索引。
```sql
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
@@ -173,18 +173,18 @@ CREATE INDEX idx_name ON users(name);
SELECT * FROM users WHERE name = "张三";
```
4. 复合索引
&emsp;&emsp;多个字段组合的索引​
4. 复合索引
多个字段组合的索引​
```sql
CREATE INDEX idx_name_age_city ON users(name, age, city);
SELECT * FROM users WHERE name = '张三';
SELECT * FROM users WHERE name = '张三' AND age = 25;
SELECT * FROM users WHERE name = '张三' AND age = 25 AND city = '北京';
SELECT * FROM users WHERE age = 25; -- 无法使用索引
SELECT * FROM users WHERE city = '北京'; -- 无法使用索引
SELECT * FROM users WHERE age = 25; -- 无法使用索引
SELECT * FROM users WHERE city = '北京'; -- 无法使用索引
```
&emsp;&emsp;复合索引的使用最左前缀原则。
&emsp;&emsp;复合索引的使用最左前缀原则。
&emsp;&emsp;`CREATE INDEX idx_name_age_city ON users(name, age, city);`相当于创建了3个索引`users(name)users(name, age)users(name, age, city)`​。
### 2.5 索引设计
@@ -317,7 +317,6 @@ COMMIT;
* 并发性: **中等**。比 `READ COMMITTED` 稍低,因为锁(特别是 Next-Key Locks可能持有更长时间覆盖更大的范围索引区间
* 数据一致性: **好**。保证了事务内读取数据的稳定性(同一行可重复读),并通过机制有效防止了幻读,满足大多数应用的需求。
* **使用场景:** MySQL 的默认选择,适用于绝大多数需要保证事务内数据读取一致性的场景,如订单处理、账户管理等。是兼顾一致性和并发性的良好平衡点。
*
4. 串行化 (SERIALIZABLE)
* 特点: 最高的隔离级别。它通过强制事务**串行执行**来实现,所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。
* 允许的问题:
@@ -357,7 +356,7 @@ COMMIT;
### 4.2 重做日志Redo Log
#### 4.2.1 概念
&emsp;&emsp;重做日志​​是 InnoDB 存储引擎特有的日志,它记录的是 ​​“物理级别” 上的页修改信息​​,主要用于 崩溃恢复Crash Recovery
&emsp;&emsp;重做日志​​是 InnoDB 存储引擎特有的日志,它记录的是 ​​“物理级别” 上的页修改信息​​,主要用于 崩溃恢复Crash Recovery
&emsp;&emsp;InnoDB 使用 WALWrite-Ahead Logging预写日志技术即 ​​事务提交时,先写 redo log再写磁盘数据页从而保证即使系统崩溃也能通过 redo log 恢复数据。
#### 4.2.2 作用
@@ -375,13 +374,13 @@ COMMIT;
### 4.4 和事务、锁之间的关系
&emsp;&emsp;例如当执行一条SQL语句时事务、锁、日志等之间的关系
1. 发起一个事务
2. 执行一系列的DML操作如 INSERT/UPDATE/DELETE
&emsp;&emsp;**涉及 ​​锁​​:对操作的数据行或表加锁,防止其他事务干扰。**
&emsp;&emsp;**涉及 ​​隔离级别​​:决定其他事务是否能“看到”你未提交的数据。**
2. 执行一系列的DML操作如 INSERT/UPDATE/DELETE
&emsp;&emsp;**涉及 ​​锁​​:对操作的数据行或表加锁,防止其他事务干扰。**
&emsp;&emsp;**涉及 ​​隔离级别​​:决定其他事务是否能“看到”你未提交的数据。**
&emsp;&emsp;**涉及 undo log如果事务回滚可以根据 undo log 恢复旧值。**
3. 事务提交commit 或 回滚rollback
&emsp;&emsp;**redo log保证即使宕机已提交事务的修改也不丢失持久性。**
&emsp;&emsp;**binlog记录数据变更用于主从复制与时间点恢复。**
3. 事务提交commit 或 回滚rollback
&emsp;&emsp;**redo log保证即使宕机已提交事务的修改也不丢失持久性。**
&emsp;&emsp;**binlog记录数据变更用于主从复制与时间点恢复。**
&emsp;&emsp;**undo log用于实现事务回滚、MVCC。**
4. **背后有日志系统默默记录一切,锁系统保障并发安全,隔离级别定义了“你能看到啥”。​**
@@ -393,7 +392,6 @@ COMMIT;
5. 锁保障并发安全,日志保障操作可恢复。两者从不同维度确保数据库的正确性。​锁​​:是在运行时控制谁可以访问哪些数据,是 ​​并发控制​​ 的手段。​​日志​​:是在磁盘上记录操作过程,是 ​​故障恢复 & 事务一致性​​ 的手段。
6. 隔离级别定义了事务间数据的可见性,而日志(尤其是 undo log 和 binlog为这种“可见性”提供了实现基础。undo log 是实现 MVCC多版本并发控制 的基础,而 MVCC 是 REPEATABLE READ 等隔离级别的关键。binlog 虽不直接影响隔离性,但它记录了事务的变更历史,是构建主从环境、实现数据恢复的基础。
## 五、优化
### 5.1 Explain 执行计划
&emsp;&emsp;`EXPLAIN`​是 MySQL 自带的一个诊断工具,它可以模拟 MySQL 查询优化器的执行过程,对`SELECT`​语句(在 MySQL 8.0 及以上版本,也支持对`UPDATE`​、`DELETE`​等语句使用)进行分析,并输出该语句的执行计划。

10
docs/Web-Backend/index.md Normal file
View File

@@ -0,0 +1,10 @@
---
layout: doc
title: Web后端
---
# 📚 内容导航
- [MySQL知识点](/Web-Backend/MySQL-Knowledge)
- [Flyway简单使用](/Web-Backend/Flyway)
- [MyBatis简介和使用](/Web-Backend/MyBatis)

View File

@@ -1,8 +0,0 @@
---
layout: doc
title: Web
---
# 📚 内容导航
- [MySQL知识点](/Web/MySQL-Knowledge)

View File

@@ -2,28 +2,28 @@
layout: home
hero:
name: "Cxx0822知识库"
text: "技术成长之路"
tagline: 记录学习笔记 | 分享开发经验 | 构建知识体系
# actions:
# - theme: brand
# text: 开始探索
# link: /web/
# - theme: alt
# text: GitHub
# link: https://github.com/你的用户名
name: "Cxx0822"
text: "拾光记"
tagline: 书山有路勤为径 | 学海无涯苦作舟
image:
src: /logo.png
alt: VitePress
actions:
- theme: brand
text: Web后端
link: /Web-Backend/
- theme: brand
text: DevOps
link: /DevOps/
- theme: brand
text: SpringBoot
link: /SpringBoot/
features:
- title: 📚 系统学习
details: 从基础到进阶,系统整理开发知识体系,帮助构建完整的技能树
- title: 💻 实战笔记
details: 记录项目开发中的实际问题与解决方案,包含代码示例和最佳实践
- title: 🚀 技术分享
details: 分享新技术探索、性能优化、架构设计等深度技术内容
- title: 📝 持续更新
details: 知识库持续维护更新,跟随技术发展趋势,保持内容的新鲜度
- title: 🔍 快速检索
details: 清晰的分类导航和搜索功能,方便快速定位所需的技术知识点
- title: 🌱 成长记录
details: 记录个人技术成长的每一个阶段,见证从新手到专家的蜕变过程
---

BIN
docs/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
docs/public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 KiB