feat:增加博客统计组件
This commit is contained in:
@@ -52,7 +52,8 @@ export default withMermaid({
|
||||
{ text: 'SpringBoot Starter原理', link: '/Web-Backend/SpringBoot/SpringBootStarter' },
|
||||
{ text: 'SpringBoot Bean简介', link: '/Web-Backend/SpringBoot/SpringBootBean' },
|
||||
{ text: 'SpringBoot3原生镜像', link: '/Web-Backend/SpringBoot/SpringBoot3-GraalVM' },
|
||||
{ text: 'SpingBoot技巧', link: '/Web-Backend/SpringBoot/SpingBoot-Skills' }
|
||||
{ text: 'SpingBoot技巧', link: '/Web-Backend/SpringBoot/SpingBoot-Skills' },
|
||||
{ text: 'SpringBoot Common', link: '/Web-Backend/SpringBoot/SpringBoot-Common' }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -125,7 +126,8 @@ export default withMermaid({
|
||||
{ text: 'SpringBoot Starter原理', link: '/Web-Backend/SpringBoot/SpringBootStarter' },
|
||||
{ text: 'SpringBoot Bean简介', link: '/Web-Backend/SpringBoot/SpringBootBean' },
|
||||
{ text: 'SpringBoot3原生镜像', link: '/Web-Backend/SpringBoot/SpringBoot3-GraalVM' },
|
||||
{ text: 'SpingBoot技巧', link: '/Web-Backend/SpringBoot/SpingBoot-Skills' }
|
||||
{ text: 'SpingBoot技巧', link: '/Web-Backend/SpringBoot/SpingBoot-Skills' },
|
||||
{ text: 'SpringBoot Common', link: '/Web-Backend/SpringBoot/SpringBoot-Common' }
|
||||
]
|
||||
},
|
||||
{ text: 'FastAPI',
|
||||
|
||||
66
docs/.vitepress/theme/components/ArticleMetadata.vue
Normal file
66
docs/.vitepress/theme/components/ArticleMetadata.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { countWord } from '../utils/functions'
|
||||
|
||||
const wordCount = ref(0)
|
||||
const imageCount = ref(0)
|
||||
|
||||
const wordTime = computed(() => {
|
||||
return ((wordCount.value / 275) * 60)
|
||||
})
|
||||
|
||||
const imageTime = computed(() => {
|
||||
const n = imageCount.value
|
||||
if (imageCount.value <= 10) {
|
||||
// 等差数列求和
|
||||
return n * 13 + (n * (n - 1)) / 2
|
||||
}
|
||||
return 175 + (n - 10) * 3
|
||||
})
|
||||
|
||||
// 阅读时间
|
||||
const readTime = computed(() => {
|
||||
return Math.ceil((wordTime.value + imageTime.value) / 60)
|
||||
})
|
||||
|
||||
|
||||
function analyze() {
|
||||
document.querySelectorAll('.meta-des').forEach(v => v.remove())
|
||||
const docDomContainer = window.document.querySelector('#VPContent')
|
||||
const imgs = docDomContainer?.querySelectorAll<HTMLImageElement>(
|
||||
'.content-container .main img'
|
||||
)
|
||||
imageCount.value = imgs?.length || 0
|
||||
const words = docDomContainer?.querySelector('.content-container .main')?.textContent || ''
|
||||
wordCount.value = countWord(words)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化时执行一次
|
||||
analyze()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="word">
|
||||
<p>
|
||||
<svg t="1724571760788" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6125" width="16" height="16"><path d="M204.8 0h477.866667l273.066666 273.066667v614.4c0 75.093333-61.44 136.533333-136.533333 136.533333H204.8c-75.093333 0-136.533333-61.44-136.533333-136.533333V136.533333C68.266667 61.44 129.706667 0 204.8 0z m307.2 607.573333l68.266667 191.146667c13.653333 27.306667 54.613333 27.306667 61.44 0l102.4-273.066667c6.826667-20.48 0-34.133333-20.48-40.96s-34.133333 0-40.96 13.653334l-68.266667 191.146666-68.266667-191.146666c-13.653333-27.306667-54.613333-27.306667-68.266666 0l-68.266667 191.146666-68.266667-191.146666c-6.826667-13.653333-27.306667-27.306667-47.786666-20.48s-27.306667 27.306667-20.48 47.786666l102.4 273.066667c13.653333 27.306667 54.613333 27.306667 61.44 0l75.093333-191.146667z" fill="#1890FF" p-id="6126"></path><path d="M682.666667 0l273.066666 273.066667h-204.8c-40.96 0-68.266667-27.306667-68.266666-68.266667V0z" fill="#52C41A" p-id="6127"></path></svg>
|
||||
字数: {{ wordCount }} 字
|
||||
<svg t="1724572797268" class="icon" viewBox="0 0 1060 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15031" width="16" height="16"><path d="M556.726857 0.256A493.933714 493.933714 0 0 0 121.929143 258.998857L0 135.021714v350.390857h344.649143L196.205714 334.482286a406.820571 406.820571 0 1 1-15.908571 312.649143H68.937143A505.819429 505.819429 0 1 0 556.726857 0.256z m-79.542857 269.531429v274.907428l249.197714 150.966857 42.422857-70.070857-212.114285-129.389714V269.787429h-79.542857z" fill="#FA8C16" p-id="15032"></path></svg>
|
||||
时长: {{ readTime }} 分钟
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.word {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
transform: translate(0px , 2px);
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import Confetti from "./components/Confetti.vue";
|
||||
import ArticleMetadata from "./components/ArticleMetadata.vue"
|
||||
import type { EnhanceAppContext } from 'vitepress'
|
||||
import { onMounted, watch, nextTick } from 'vue'
|
||||
import { useRoute } from 'vitepress'
|
||||
@@ -27,5 +28,6 @@ export default {
|
||||
},
|
||||
enhanceApp({ app }: EnhanceAppContext) {
|
||||
app.component("Confetti", Confetti);
|
||||
app.component("ArticleMetadata", ArticleMetadata);
|
||||
},
|
||||
};
|
||||
|
||||
19
docs/.vitepress/theme/utils/functions.ts
Normal file
19
docs/.vitepress/theme/utils/functions.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
const pattern
|
||||
= /[a-zA-Z0-9_\u0392-\u03C9\u00C0-\u00FF\u0600-\u06FF\u0400-\u04FF]+|[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF\u3040-\u309F\uAC00-\uD7AF]+/g
|
||||
|
||||
export function countWord(data: string) {
|
||||
const m = data.match(pattern)
|
||||
let count = 0
|
||||
if (!m) {
|
||||
return 0
|
||||
}
|
||||
for (let i = 0; i < m.length; i += 1) {
|
||||
if (m[i].charCodeAt(0) >= 0x4E00) {
|
||||
count += m[i].length
|
||||
}
|
||||
else {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
## 一、基础概念
|
||||
|
||||
## 二、Docker部署
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、Jenkins安装
|
||||
## 1.1 简介
|
||||
  Jenkins 是一款开源的持续集成(Continuous Integration, CI) 和持续交付(Continuous Delivery, CD) 自动化工具,广泛应用于软件开发流程中,帮助团队实现代码构建、测试、部署的自动化,从而提升开发效率、减少人为错误,并确保软件质量。
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
## 一、基础概念
|
||||
### 1.1 Fluent Bit
|
||||
  [Fluent Bit](https://fluentbit.io/) 是一个开源的、轻量级、高性能的日志处理器和转发器。Fluent Bit 的核心任务是:**从各种来源收集日志、指标和追踪数据,进行处理和过滤,然后将其发送到一个或多个目的地。**
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、基础语法
|
||||
## 1.1 变量与常量
|
||||
```dart
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 应用核心Widget
|
||||
```dart
|
||||
// Flutter 应用的入口,配置主题、路由、国际化等。
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 安卓签名
|
||||
  每次安装/升级软件必须使用同一个签名,否则会将本地数据全部清空。
|
||||
## 1. 生成密钥库文件
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、简介
|
||||
  [FastAPI](https://fastapi.tiangolo.com/zh/) 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 并基于标准的 Python 类型提示。
|
||||
  安装:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、基础概念
|
||||
## 1.1 OAuth2 Password Bearer 模式
|
||||
  用于**用户名+密码**登录,获取**access_token**。
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、简介
|
||||
  Flyway 是一个开源的数据库版本控制工具,它极大地简化了数据库的迁移和版本管理。它的核心思想是像**用 Git 管理代码一样来管理数据库的结构**。
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、简介
|
||||
  [MyBatis](https://mybatis.org/mybatis-3/)是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# MySQL知识点
|
||||
## 一、基础知识
|
||||
### 1.1 数据类型
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
# TCP
|
||||
## 定义
|
||||
  TCP(Transmission Control Protocol,传输控制协议)是互联网核心的面向连接、可靠、字节流的传输层协议,工作在 OSI 模型的传输层(TCP/IP 模型的传输层),基于 IP 协议提供端到端的可靠数据传输服务。它是 HTTP、HTTPS、WebSocket、MQTT 等应用层协议的底层依赖,核心目标是解决 IP 协议 “无连接、不可靠、无顺序” 的缺陷,确保数据在不可靠的网络中准确、完整、有序地传输。
|
||||
<ArticleMetadata />
|
||||
|
||||
## 特性
|
||||
### 面向连接
|
||||
# 一、TCP
|
||||
## 1.1 定义
|
||||
  TCP(Transmission Control Protocol,传输控制协议)是互联网核心的**面向连接、可靠、字节流**的传输层协议,工作在 OSI 模型的传输层(TCP/IP 模型的传输层),基于 IP 协议提供端到端的可靠数据传输服务。它是 HTTP、HTTPS、WebSocket、MQTT 等应用层协议的底层依赖,核心目标是解决 IP 协议 “无连接、不可靠、无顺序” 的缺陷,确保数据在不可靠的网络中准确、完整、有序地传输。
|
||||
|
||||
## 1.2 特性
|
||||
### 1.2.1 面向连接
|
||||
  通信前必须完成「三次握手」建立连接,通信后通过「四次挥手」释放连接:
|
||||
- 三次握手:客户端发 SYN → 服务器回 SYN+ACK → 客户端发 ACK(确保双方收发能力正常);
|
||||
- 四次挥手:客户端发 FIN → 服务器回 ACK → 服务器发 FIN → 客户端回 ACK(确保数据传输完毕)。
|
||||
|
||||
### 可靠传输
|
||||
### 1.2.2 可靠传输
|
||||
- 序号与确认号:每个字节都有序号,接收方收到后回复确认号,未收到则发送方重传;
|
||||
- 超时重传:发送方未在规定时间收到确认,自动重传数据;
|
||||
- 流量控制:通过滑动窗口机制,防止发送方发送过快导致接收方缓冲区溢出;
|
||||
- 拥塞控制:通过慢启动、拥塞避免等算法,适应网络带宽变化。
|
||||
|
||||
### 面向字节流
|
||||
### 1.2.3 面向字节流
|
||||
  TCP 将应用层数据视为连续的字节流,不保留应用层数据的边界(与 UDP 的 “数据报” 模式不同):
|
||||
- 发送方:应用层数据被拆分为 TCP 报文段(Segment)发送,拆分规则由 TCP 协议决定(如 MSS 限制)。
|
||||
- 接收方:将收到的报文段按顺序重组为完整的字节流,再交给应用层,确保数据顺序与发送时一致。
|
||||
|
||||
### 有序传输
|
||||
### 1.2.4 有序传输
|
||||
  TCP 报文段头部包含 “序号(Sequence Number)” 和 “确认号(Acknowledgment Number)”:
|
||||
- 序号(SN):标识发送方当前发送的字节流位置(如序号为 100 表示当前报文段的第一个字节是整个字节流的第 100 字节)。
|
||||
- 确认号(ACK):标识接收方期望下次接收的字节流位置(如确认号为 200 表示已正确接收前 199 字节,下次需从 200 字节开始接收)。
|
||||
- 接收方通过序号排序报文段,丢弃重复报文,确保按发送顺序交付数据。
|
||||
|
||||
### 全双工通信
|
||||
### 1.2.5 全双工通信
|
||||
  TCP 连接是双向的,双方可同时发送和接收数据,无需等待对方结束发送:
|
||||
- 每个方向都有独立的发送缓冲区和接收缓冲区,以及独立的滑动窗口用于流量控制。
|
||||
- 示例:客户端发送数据的同时,服务器可同步向客户端返回响应,无需等待客户端发送完毕。
|
||||
|
||||
## 优缺点
|
||||
## 1.3 优缺点
|
||||
  优点:
|
||||
1. 可靠、有序、无丢包
|
||||
2. 支持流量 / 拥塞控制
|
||||
@@ -41,17 +43,404 @@
|
||||
2. 头部开销大(20-60 字节)
|
||||
3. 不适合实时性要求极高的场景(如直播低延迟)
|
||||
|
||||
## 1.4 Python实现
|
||||
  TCP服务器:
|
||||
```python
|
||||
import socket
|
||||
import signal
|
||||
import threading
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
from model.mqtt import UNKNOWN_MESSAGE
|
||||
from processor.tcp_processor import TcpMessageProcessor
|
||||
from config.logger_config import logger
|
||||
|
||||
|
||||
class TCPServer:
|
||||
def __init__(self):
|
||||
load_dotenv()
|
||||
|
||||
self.host = os.getenv("TCP_HOST", 'localhost')
|
||||
self.port = int(os.getenv("TCP_PORT", 9100))
|
||||
|
||||
self.max_workers = 10
|
||||
self.timeout = 300
|
||||
|
||||
self.server_socket = None
|
||||
self.running = False
|
||||
self.thread_pool = ThreadPoolExecutor(max_workers=self.max_workers)
|
||||
self.message_processor = TcpMessageProcessor()
|
||||
|
||||
# 已经连接的客户端
|
||||
self.clients = {}
|
||||
self.client_lock = threading.Lock()
|
||||
|
||||
self.server_thread = None
|
||||
|
||||
signal.signal(signal.SIGTERM, self._handle_signal)
|
||||
signal.signal(signal.SIGINT, self._handle_signal)
|
||||
|
||||
def _handle_signal(self, signum, frame):
|
||||
"""处理终止信号,触发优雅关闭"""
|
||||
logger.info(f"收到信号 {signum},准备关闭服务器...")
|
||||
self.running = False
|
||||
|
||||
def _start_loop(self):
|
||||
"""启动服务器"""
|
||||
try:
|
||||
# 创建 TCP/IP socket AF_INET: IPv4地址族 SOCK_STREAM: TCP协议(面向连接)
|
||||
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
# 设置SO_REUSEADDR选项,允许重用地址和端口,避免服务端重启时出现 “地址已被占用” 的错误
|
||||
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
# 绑定到指定主机和端口 0.0.0.0 表示所有主机
|
||||
self.server_socket.bind((self.host, self.port))
|
||||
# 设置最大等待连接数
|
||||
self.server_socket.listen(5)
|
||||
# 设置socket超时时间(1.0秒)
|
||||
self.server_socket.settimeout(1.0)
|
||||
self.running = True
|
||||
|
||||
logger.info(f"TCP服务器启动,监听 {self.host}:{self.port} "
|
||||
f"(最大线程: {self.max_workers}, 超时: {self.timeout}s)")
|
||||
|
||||
while self.running:
|
||||
try:
|
||||
# accept() 会阻塞直到有客户端连接
|
||||
# client_socket: 与客户端通信的新socket client_address: 客户端地址(ip, port)元组
|
||||
client_socket, client_address = self.server_socket.accept()
|
||||
# 获取客户端IP
|
||||
client_ip = client_address[0]
|
||||
# 设置客户端socket超时
|
||||
# 防止客户端长时间不发送数据
|
||||
client_socket.settimeout(self.timeout)
|
||||
logger.info(f"新连接: {client_address}")
|
||||
|
||||
# 存储客户端
|
||||
with self.client_lock:
|
||||
self.clients[client_ip] = client_socket
|
||||
|
||||
# 提交到线程池处理
|
||||
# 提交到线程池是为了让服务器能同时服务多个客户端,而不让一个慢客户端阻塞所有其他客户端
|
||||
self.thread_pool.submit(self.handle_client, client_socket, client_ip)
|
||||
except socket.timeout:
|
||||
continue
|
||||
except Exception as e:
|
||||
if self.running:
|
||||
logger.error(f"接受连接失败: {str(e)}")
|
||||
except Exception as e:
|
||||
logger.error(f"TCP服务器启动失败: {str(e)}")
|
||||
finally:
|
||||
self.stop()
|
||||
|
||||
def start(self):
|
||||
self.server_thread = threading.Thread(target=self._start_loop, daemon=True)
|
||||
self.server_thread.start()
|
||||
|
||||
def stop(self):
|
||||
if not self.running:
|
||||
return
|
||||
|
||||
self.running = False
|
||||
logger.info("开始关闭服务器...")
|
||||
|
||||
# 移除客户端
|
||||
with self.client_lock:
|
||||
for client_socket in self.clients.values():
|
||||
try:
|
||||
client_socket.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"关闭客户端连接失败: {str(e)}")
|
||||
self.clients.clear()
|
||||
|
||||
# 关闭线程池
|
||||
self.thread_pool.shutdown(wait=True)
|
||||
logger.info("所有客户端处理线程已结束")
|
||||
|
||||
# 关闭连接
|
||||
if self.server_socket:
|
||||
self.server_socket.close()
|
||||
logger.info(f"服务器已关闭({self.host}:{self.port})")
|
||||
|
||||
def handle_client(self, client_socket, client_ip):
|
||||
"""处理客户端连接"""
|
||||
try:
|
||||
while True:
|
||||
data = client_socket.recv(1024)
|
||||
if not data:
|
||||
logger.info(f"客户端 {client_ip} 主动断开连接")
|
||||
break
|
||||
|
||||
message = data.decode('utf-8').strip()
|
||||
logger.info(f"收到 {client_ip} 的消息: {message}")
|
||||
|
||||
# 放到消息处理器里面处理
|
||||
response = self.message_processor.process(message)
|
||||
|
||||
# 回复消息
|
||||
if response != UNKNOWN_MESSAGE:
|
||||
client_socket.sendall(response.encode('utf-8'))
|
||||
logger.info(f"回复 {client_ip}: {response}")
|
||||
except socket.timeout:
|
||||
logger.warning(f"客户端 {client_ip} 超时未活动")
|
||||
except Exception as e:
|
||||
logger.error(f"处理 {client_ip} 出错: {str(e)}")
|
||||
finally:
|
||||
# 异常情况下关闭连接
|
||||
with self.client_lock:
|
||||
if client_ip in self.clients:
|
||||
del self.clients[client_ip]
|
||||
|
||||
try:
|
||||
client_socket.close()
|
||||
logger.info(f"客户端 {client_ip} 连接已关闭")
|
||||
except Exception as e:
|
||||
logger.warning(f"关闭 {client_ip} 连接失败: {str(e)}")
|
||||
|
||||
def send_to_client(self, client_ip, message):
|
||||
# 先获取客户端连接(加锁保护)
|
||||
with self.client_lock:
|
||||
client_socket = self.clients.get(client_ip)
|
||||
if not client_socket:
|
||||
logger.warning(f"客户端 {client_ip} 不存在或已断开连接")
|
||||
return False
|
||||
|
||||
# 发送消息
|
||||
try:
|
||||
client_socket.sendall(message.encode('utf-8'))
|
||||
logger.info(f"主动发送消息给 {client_ip}: {message}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"向 {client_ip} 发送消息失败: {str(e)}")
|
||||
# 发送失败时移除无效连接
|
||||
with self.client_lock:
|
||||
if client_ip in self.clients:
|
||||
del self.clients[client_ip]
|
||||
return False
|
||||
|
||||
|
||||
tcp_server = TCPServer()
|
||||
```
|
||||
|
||||
  main.py:
|
||||
```python
|
||||
from endpoint.tcp_server import tcp_server
|
||||
from config.logger_config import logger
|
||||
import time
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
tcp_server.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
logger.info("收到终止信号,开始关闭程序...")
|
||||
finally:
|
||||
tcp_server.stop()
|
||||
logger.info("程序已退出")
|
||||
except Exception as e:
|
||||
logger.critical(f"程序启动失败: {str(e)}", exc_info=True)
|
||||
exit(1)
|
||||
```
|
||||
|
||||
  启动tcp_server后,通过while循环防止主线程退出,从而让后台的TCP服务器线程能继续运行。
|
||||
  在TCPServer中,通过while循环持续接收tcp客户端的连接,每当有一个客户端连接时,会提交到线程池中去处理,通过自定义消息处理器,将处理完的结果返回给客户端。
|
||||
  TCP消息处理器:
|
||||
::: code-group
|
||||
```python [抽象消息处理器]
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class AbstractDeviceMessageParser(ABC):
|
||||
"""设备消息解析器基类"""
|
||||
|
||||
@abstractmethod
|
||||
def check(self, message):
|
||||
"""判断当前解析器是否能处理该消息"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def parse(self, message):
|
||||
"""解析消息内容"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def response(self, parsed_data):
|
||||
"""根据解析后的数据生成响应"""
|
||||
pass
|
||||
```
|
||||
|
||||
```python [示例消息处理器]
|
||||
from datetime import datetime
|
||||
|
||||
from model.band import BandData
|
||||
from model.mqtt import MqttTopic, MqttData, UNKNOWN_MESSAGE
|
||||
from parser.abstract_parsers import AbstractDeviceMessageParser
|
||||
import re
|
||||
|
||||
from utils.index import str_length_to_4hex
|
||||
from config.logger_config import logger
|
||||
from endpoint.mqtt_client import mqtt_client
|
||||
|
||||
|
||||
class JuweiBandParser(AbstractDeviceMessageParser):
|
||||
"""聚伟手环消息解析器"""
|
||||
|
||||
def __init__(self):
|
||||
# 聚伟手环消息格式: [MNYD*设备ID*内容长度*内容]
|
||||
self.pattern = r'MNYD'
|
||||
self.vendor = "聚伟手环"
|
||||
self.tag = "MNYD"
|
||||
self.device_id = ""
|
||||
|
||||
def check(self, message):
|
||||
"""检查是否为聚伟手环的消息格式"""
|
||||
return re.search(self.pattern, message) is not None
|
||||
|
||||
def parse(self, message):
|
||||
"""解析聚伟手环消息"""
|
||||
try:
|
||||
parts = message.strip("[]").split("*")
|
||||
|
||||
self.device_id = parts[1]
|
||||
content = parts[3]
|
||||
|
||||
return {
|
||||
'vendor': self.vendor,
|
||||
'device_id': self.device_id,
|
||||
'content': content,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"解析{self.vendor}消息出错: {str(e)}")
|
||||
return None
|
||||
|
||||
def publish_vital_data(self, item, value):
|
||||
mqtt_client.publish(
|
||||
topic=MqttTopic.JuWei_Band_Post.value,
|
||||
payload=MqttData(
|
||||
deviceIp="",
|
||||
deviceId=self.device_id,
|
||||
payload=BandData(item=item, value=value).model_dump_json(),
|
||||
).model_dump_json())
|
||||
|
||||
def response(self, parsed_data):
|
||||
"""生成聚伟手环的响应消息"""
|
||||
if not parsed_data:
|
||||
return UNKNOWN_MESSAGE
|
||||
|
||||
# 处理不同命令
|
||||
content = parsed_data['content']
|
||||
|
||||
parts = content.split(",", 1)
|
||||
content_tag = parts[0] if len(parts) > 0 else ""
|
||||
content_value = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
replay = UNKNOWN_MESSAGE
|
||||
|
||||
logger.info(f"解析{content_tag}消息")
|
||||
|
||||
match content_tag:
|
||||
# PING消息 [MNYD*334588000000156*0004*PING]
|
||||
case "PING":
|
||||
replay = "PING,1"
|
||||
# 日期,步数,翻滚次数,电量百分数,里程数(km)
|
||||
# [MNYD*334588000000156*0014*KA,120414,50,100,100,100.12]
|
||||
case "KA":
|
||||
replay = content_tag
|
||||
# 位置数据上报
|
||||
# case "UD":
|
||||
# replay = ""
|
||||
# 报警数据上报
|
||||
# [MNYD*334588000000156*00CD*AL,180916,064153,A,22.570512,N,113.8623267,
|
||||
# E,0.00,154.8,0.0,11,100,100,0,0,00100018,7,0,460,1,9529,
|
||||
# 21809,155,9529,21242,132,9529,21405,131,9529,63554,131,9529,
|
||||
# 63555,130,9529,63556,118,9529,21869,116,0,12.4]
|
||||
case "AL":
|
||||
replay = content_tag
|
||||
# 获取服务器端时间
|
||||
# [MNYD*YYYYYYYYYYYYYYY*LEN*LGZONE]
|
||||
case "LGZONE":
|
||||
now = datetime.now()
|
||||
current_date = now.date().strftime("%Y-%m-%d")
|
||||
current_time = now.time().strftime("%H:%M:%S")
|
||||
replay = f"{content_tag},+8,{current_time},{current_date}"
|
||||
# 请求位置数据 TODO
|
||||
case "WG":
|
||||
replay = content_tag
|
||||
# 请求电话本设置信息 TODO
|
||||
case "PHLQ":
|
||||
replay = "PHL"
|
||||
# 请求SOS设置信息 TODO
|
||||
case "SOS":
|
||||
replay = content_tag
|
||||
# 终端心率上传
|
||||
case "heart":
|
||||
self.publish_vital_data(content_tag, content_value)
|
||||
replay = content_tag
|
||||
# 上传体温数据 [MNYD*334588000000156*0009*temp,36.2]
|
||||
case "temp":
|
||||
self.publish_vital_data(content_tag, content_value)
|
||||
replay = content_tag
|
||||
# 上传血压数据 [MNYD*334588000000156*000C*blood,150,70]
|
||||
case "blood":
|
||||
self.publish_vital_data(content_tag, content_value)
|
||||
replay = content_tag
|
||||
# 上传血氧数据 [MNYD*334588000000156*0009*oxygen,97]
|
||||
case "oxygen":
|
||||
self.publish_vital_data(content_tag, content_value)
|
||||
replay = content_tag
|
||||
# 上传睡眠数据报告
|
||||
case "SLEEPRPT":
|
||||
replay = "SLEEP"
|
||||
case _:
|
||||
logger.info("不需要回复")
|
||||
return UNKNOWN_MESSAGE
|
||||
|
||||
return f"[{self.tag}*{parsed_data['device_id']}*{str_length_to_4hex(replay)}*{replay}]"
|
||||
```
|
||||
|
||||
```python [消息处理器]
|
||||
from model.mqtt import UNKNOWN_MESSAGE
|
||||
from parser.juwei_band_parser import JuweiBandParser
|
||||
from config.logger_config import logger
|
||||
|
||||
class TcpMessageProcessor:
|
||||
"""消息处理器,负责将消息路由到正确的设备解析器"""
|
||||
|
||||
def __init__(self):
|
||||
# 注册所有支持的设备解析器
|
||||
self.parsers = [JuweiBandParser()]
|
||||
|
||||
def process(self, message):
|
||||
"""处理消息,返回响应"""
|
||||
# 尝试找到能处理该消息的解析器
|
||||
for parser in self.parsers:
|
||||
if parser.check(message):
|
||||
parsed_data = parser.parse(message)
|
||||
if parsed_data:
|
||||
logger.info(f"处理{parsed_data['vendor']}消息: {message}")
|
||||
return parser.response(parsed_data)
|
||||
|
||||
# 没有找到合适的解析器
|
||||
logger.warning(f"未识别的消息格式: {message}")
|
||||
return UNKNOWN_MESSAGE
|
||||
```
|
||||
:::
|
||||
|
||||
|
||||
# HTTP
|
||||
## 定义
|
||||
  HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网的核心协议之一,用于客户端(如浏览器、App)与服务器之间的通信,是万维网(WWW)数据交换的基础。它定义了请求 / 响应的格式、传输规则和状态码等核心机制,支持从简单文本到复杂多媒体(图片、视频、文件)的传输,也是现代 Web 应用的底层通信标准。
|
||||
  HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网的核心协议之一,用于**客户端(如浏览器、App)与服务器之间的通信**,是万维网(WWW)数据交换的基础。它定义了请求 / 响应的格式、传输规则和状态码等核心机制,支持从简单文本到复杂多媒体(图片、视频、文件)的传输,也是现代 Web 应用的底层通信标准。
|
||||
|
||||
## 特性
|
||||
### 请求 - 响应模式
|
||||
  通信由客户端主动发起请求,服务器接收后处理并返回响应,不存在服务器主动向客户端推送数据的情况(HTTP/2 引入 Server Push 扩展,可主动推送关联资源)。
|
||||
  一次完整通信流程:客户端建立连接 → 发送请求 → 服务器处理 → 返回响应 → 连接关闭(HTTP/1.1 默认开启长连接 Keep-Alive)。
|
||||
  一次完整通信流程:**客户端建立连接 → 发送请求 → 服务器处理 → 返回响应 → 连接关闭**(HTTP/1.1 默认开启长连接 Keep-Alive)。
|
||||
|
||||
### 无状态
|
||||
  服务器不会保存客户端的会话状态(如登录状态、浏览记录),每次请求都是独立的,服务器无法通过协议本身识别连续请求是否来自同一客户端。通过 Cookie、Session、Token(如 JWT)等机制补充状态管理。
|
||||
  **服务器不会保存客户端的会话状态**(如登录状态、浏览记录),每次请求都是独立的,服务器无法通过协议本身识别连续请求是否来自同一客户端。通过 Cookie、Session、Token(如 JWT)等机制补充状态管理。
|
||||
|
||||
## 版本
|
||||
### HTTP/1.0(1996 年)
|
||||
@@ -159,8 +548,8 @@ Set-Cookie: sessionId=abc123; Path=/
|
||||
|
||||
# WebSocket
|
||||
## 定义
|
||||
  它的核心特点是:一旦客户端与服务器建立连接,双方就可以在这个连接上实时、双向地发送数据,无需像 HTTP 那样每次通信都由客户端发起请求,非常适合实时通信场景(如聊天、直播弹幕、实时数据监控、在线协作等)。
|
||||
|
||||
  WebSocket 是一种**全双工、双向、持久化的网络通信协议**(属于应用层协议),由 HTML5 规范定义,专门解决 HTTP 协议无法实现服务器主动向客户端推送数据的问题。
|
||||
  它的核心特点是:**一旦客户端与服务器建立连接,双方就可以在这个连接上实时、双向地发送数据**,无需像 HTTP 那样每次通信都由客户端发起请求,非常适合实时通信场景(如聊天、直播弹幕、实时数据监控、在线协作等)。
|
||||
|
||||
| 特性 | HTTP | WebSocket |
|
||||
|---------------------|-------------------------------|-------------------------------|
|
||||
@@ -209,7 +598,7 @@ Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= # 服务器加密后的密
|
||||
|
||||
# MQTT
|
||||
## 定义
|
||||
|
||||
  MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是一种**轻量级、低带宽、低功耗的发布 / 订阅(Publish/Subscribe)模式物联网(IoT)通信协议**,由 IBM 于 1999 年设计,核心目标是解决受限设备(如传感器、嵌入式设备)和低带宽、不稳定网络环境下的高效数据传输问题。
|
||||
|
||||
## 架构
|
||||
- 发布者(Publisher):发送消息的设备 / 服务;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 简介
|
||||
  [RustFS](https://rustfs.com.cn/) 是一个基于 Rust 构建的高性能分布式对象存储系统。
|
||||
  具体以下特点:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、JsonView
|
||||
  JsonView 是 Jackson 提供的注解,用于控制对象序列化/反序列化时包含哪些字段。可以实现:
|
||||
- 不同接口返回不同字段
|
||||
|
||||
871
docs/Web-Backend/SpringBoot/SpringBoot-Common.md
Normal file
871
docs/Web-Backend/SpringBoot/SpringBoot-Common.md
Normal file
@@ -0,0 +1,871 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、框架说明
|
||||
  本框架基于Spring Boot3框架二次开发,增加了依赖包管理和启动项配置等功能。
|
||||
|
||||
# 二、项目结构
|
||||
|
||||
| 项目模块 | 模块含义 | 主要功能 |
|
||||
| :-----------: | :-------------------: | :---------------------------------: |
|
||||
| starters | SpringBoot 启动项 | 主要包括web、jdbc和log启动项配置 |
|
||||
| autoconfigure | staters具体启动配置类 | 主要包括web和jdbc具体的启动项配置类 |
|
||||
| dependencies | 依赖项 | 主要包括本框架中的依赖包管理 |
|
||||
| framework | 通用配置 | 主要包括web和data的一些通用工具方法 |
|
||||
|
||||
# 三、项目说明
|
||||
## 3.1 common模块
|
||||
  该模块主要声明项目结构,包括autoconfigure、dependencies、framework、starter-parent和starters等模块。
|
||||
```xml
|
||||
<modules>
|
||||
<!-- modules表示聚合关系,即common有以下模块 -->
|
||||
<module>dependencies</module>
|
||||
<module>starters</module>
|
||||
<module>framework</module>
|
||||
<module>autoconfigure</module>
|
||||
<module>starter-parent</module>
|
||||
</modules>
|
||||
```
|
||||
|
||||
## 3.2 dependencies模块
|
||||
  该模块为其他模块的父模块,声明了一些常用依赖包及版本,通过`<dependencyManagement>`管理,只声明依赖的版本,并不会实际引入依赖。
|
||||
  后续有新增依赖时,需要先在dependencies模块声明版本,然后在相应的starter模块中增加依赖。
|
||||
```xml
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
|
||||
<spring.boot.version>3.5.0</spring.boot.version>
|
||||
<springdoc.version>2.7.0</springdoc.version>
|
||||
|
||||
<logback-more-appenders.version>1.8.8</logback-more-appenders.version>
|
||||
<fluency-fluentd.version>2.7.0</fluency-fluentd.version>
|
||||
|
||||
<mybatis-spring.version>3.0.4</mybatis-spring.version>
|
||||
<mybatis.plus.version>3.5.12</mybatis.plus.version>
|
||||
<sa.token.version>1.43.0</sa.token.version>
|
||||
<hutool.version>5.8.26</hutool.version>
|
||||
|
||||
<commons.io.version>2.19.0</commons.io.version>
|
||||
<commons.collections.version>4.4</commons.collections.version>
|
||||
<commons-lang3.version>3.14.0</commons-lang3.version>
|
||||
<commons.net.version>3.9.0</commons.net.version>
|
||||
<guava.version>31.1-jre</guava.version>
|
||||
<mapstruct.version>1.5.5.Final</mapstruct.version>
|
||||
<yitter.idgenerator.version>1.0.6</yitter.idgenerator.version>
|
||||
|
||||
<lombok.version>1.18.30</lombok.version>
|
||||
<lombok.mapstruct.version>0.2.0</lombok.mapstruct.version>
|
||||
|
||||
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
|
||||
<docker-maven-plugin.version>0.41.0</docker-maven-plugin.version>
|
||||
<native-maven-plugin.version>0.10.2</native-maven-plugin.version>
|
||||
</properties>
|
||||
|
||||
<!-- dependencyManagement部分只声明依赖的版本,并不会实际引入依赖。
|
||||
需要在具体的模块中显式声明依赖,才能让模块使用这些库 -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- SpringBoot依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- SpringDoc OpenAPI + Swagger UI -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 扩展 logback appender -->
|
||||
<dependency>
|
||||
<groupId>com.sndyuk</groupId>
|
||||
<artifactId>logback-more-appenders</artifactId>
|
||||
<version>${logback-more-appenders.version}</version>
|
||||
</dependency>
|
||||
<!-- Fluentd 日志搜集和转发 -->
|
||||
<dependency>
|
||||
<groupId>org.komamitsu</groupId>
|
||||
<artifactId>fluency-fluentd</artifactId>
|
||||
<version>${fluency-fluentd.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- SaToken BOM -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-bom</artifactId>
|
||||
<version>${sa.token.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- hutool BOM -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<version>${mybatis-spring.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis-Plus Maven BOM -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-bom</artifactId>
|
||||
<version>${mybatis.plus.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- IO工具类 -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons.io.version}</version>
|
||||
</dependency>
|
||||
<!-- 集合工具类 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
<version>${commons.collections.version}</version>
|
||||
</dependency>
|
||||
<!-- 字符串工具类 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
<!-- 网络工具类 -->
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>${commons.net.version}</version>
|
||||
</dependency>
|
||||
<!-- 工具类(集合、缓存、并发、IO、字符串) -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava-bom</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 实体映射工具类 -->
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 雪花ID生成器 -->
|
||||
<dependency>
|
||||
<groupId>com.github.yitter</groupId>
|
||||
<artifactId>yitter-idgenerator</artifactId>
|
||||
<version>${yitter.idgenerator.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- starter中的依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-logging</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-web</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-jdbc</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>docker-maven-plugin</artifactId>
|
||||
<version>${docker-maven-plugin.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.graalvm.buildtools</groupId>
|
||||
<artifactId>native-maven-plugin</artifactId>
|
||||
<version>${native-maven-plugin.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
```
|
||||
|
||||
::: tip
|
||||
dependencyManagement部分只声明依赖的版本,并不会实际引入依赖。
|
||||
需要在具体的模块中显式声明依赖,才能让模块使用这些库
|
||||
:::
|
||||
|
||||
## 3.3 framework模块
|
||||
  该模块主要是一些通用的配置。
|
||||
|
||||
### 3.3.1 data
|
||||
  主要提供了2个数据库实体类的基类。
|
||||
::: code-group
|
||||
```java [AbstractIdEntity]
|
||||
@Getter
|
||||
@Setter
|
||||
public abstract class AbstractIdEntity {
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
}
|
||||
```
|
||||
|
||||
```java [AbstractEntity]
|
||||
@Getter
|
||||
@Setter
|
||||
public abstract class AbstractEntity extends AbstractIdEntity {
|
||||
@TableField(value = "create_time", fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(value = "create_by", fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
### 3.3.2 web
|
||||
  主要提供了自定义异常处理类。
|
||||
::: code-group
|
||||
```java [ErrorResponse]
|
||||
/**
|
||||
* 错误响应
|
||||
*/
|
||||
public final class ErrorResponse {
|
||||
/**
|
||||
* 自定义code
|
||||
*/
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* 自定义消息
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
public ErrorResponse(String message) {
|
||||
this.code = "Unspecified";
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public ErrorResponse(String code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java [CustomException]
|
||||
/**
|
||||
* 自定义异常类
|
||||
*/
|
||||
public class CustomException extends AbstractException {
|
||||
|
||||
public CustomException(String errorMessage) {
|
||||
super(errorMessage);
|
||||
}
|
||||
|
||||
public CustomException(String code, String errorMessage) {
|
||||
super(code, errorMessage);
|
||||
}
|
||||
|
||||
public CustomException(String errorMessage, Exception innerException) {
|
||||
super(errorMessage, innerException);
|
||||
}
|
||||
|
||||
public CustomException(String code, String errorMessage, Exception innerException) {
|
||||
super(code, errorMessage, innerException);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```java [AbstractException]
|
||||
/**
|
||||
* 抽象异常类
|
||||
*/
|
||||
public abstract class AbstractException extends RuntimeException {
|
||||
protected String code;
|
||||
|
||||
public AbstractException(String errorMessage) {
|
||||
super(errorMessage);
|
||||
}
|
||||
|
||||
public AbstractException(String code, String errorMessage) {
|
||||
super(errorMessage);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public AbstractException(String errorMessage, Exception innerException) {
|
||||
super(errorMessage, innerException);
|
||||
}
|
||||
|
||||
public AbstractException(String code, String errorMessage, Exception innerException) {
|
||||
super(errorMessage, innerException);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
### 3.4 starters模块
|
||||
  该模块为整个项目的核心模块,在实际项目中,通过引入相应的starter模块,并配合autoconfigure模块中的功能,即可实现快速自动装配。
|
||||
  该模块的pom.xml声明的依赖会注入到实际的项目中。
|
||||
  在springboot3中,可以在**main->resources->META-INF->spring->org.springframework.boot.autoconfigure.AutoConfiguration.imports**中声明需要自动执行的类。
|
||||
|
||||
#### 3.4.1 stater-jdbc
|
||||
  该模块主要提供数据库相关的配置功能。
|
||||
  目前实现的功能有:
|
||||
1. 配置MybatisPlus拦截器,添加乐观锁和分页插件。
|
||||
2. 自定义MybatisPlus ID生成器(雪花ID)。
|
||||
3. 配置MybatisPlus自动填充字段(create_time、create_by、update_time和update_by)。
|
||||
|
||||
::: code-group
|
||||
```java [JdbcAutoConfiguration]
|
||||
/**
|
||||
* jdbc自动配置
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(SqlSessionFactory.class)
|
||||
@Import({YitterGenerator.class, MybatisMetaObjectHandler.class})
|
||||
public class JdbcAutoConfiguration {
|
||||
/**
|
||||
* MybatisPlus拦截器
|
||||
* @return 拦截器
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 乐观锁插件
|
||||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||
// 分页插件
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java [YitterGenerator]
|
||||
/**
|
||||
* 雪花id生成器
|
||||
*/
|
||||
public class YitterGenerator implements IdentifierGenerator {
|
||||
|
||||
@Override
|
||||
public Number nextId(Object entity) {
|
||||
return YitIdHelper.nextId();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```java [MybatisMetaObjectHandler]
|
||||
/**
|
||||
* Mybatis Plus自动填充
|
||||
*/
|
||||
public class MybatisMetaObjectHandler implements MetaObjectHandler {
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
this.strictInsertFill(metaObject, Constants.CREATE_TIME_FLAG, LocalDateTime.class, LocalDateTime.now());
|
||||
this.strictInsertFill(metaObject, Constants.UPDATE_TIME_FLAG, LocalDateTime.class, LocalDateTime.now());
|
||||
|
||||
if (StpUtil.isLogin()) {
|
||||
this.strictInsertFill(metaObject, Constants.CREATE_BY_FLAG, String.class, StpUtil.getLoginIdAsString());
|
||||
this.strictInsertFill(metaObject, Constants.UPDATE_BY_FLAG, String.class, StpUtil.getLoginIdAsString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
this.strictUpdateFill(metaObject, Constants.UPDATE_TIME_FLAG, LocalDateTime.class, LocalDateTime.now());
|
||||
|
||||
if (StpUtil.isLogin()) {
|
||||
this.strictUpdateFill(metaObject, Constants.UPDATE_BY_FLAG, String.class, StpUtil.getLoginIdAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
  `@ConditionalOnClass(SqlSessionFactory.class)`表示只有存在SqlSessionFactory类时,这个配置类才会生效。即如果没有 MyBatis 相关依赖,这个配置类会被 Spring 完全忽略。
|
||||
  `@Import`用于导入其他配置类或组件到当前配置类中。
|
||||
|
||||
#### 3.4.2 starter-web
|
||||
  该模块主要提供web相关的配置功能。
|
||||
  目前实现的功能有:
|
||||
1. 注册审计拦截器
|
||||
2. 注册Sa-Token拦截器
|
||||
3. 注册安全拦截器
|
||||
4. 配置CORS跨越
|
||||
5. 全局异常处理器
|
||||
|
||||
::: code-group
|
||||
```java [ServerAutoConfiguration]
|
||||
@Configuration()
|
||||
@Import({DefaultExceptionAdvice.class, AuditBodyAdvice.class, CustomProperties.class})
|
||||
public class ServerAutoConfiguration implements WebMvcConfigurer {
|
||||
@Resource
|
||||
private CustomProperties customProperties;
|
||||
|
||||
/**
|
||||
* 注册拦截器 需要实现 WebMvcConfigurer 接口
|
||||
* @param registry 注册器
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 注册安全拦截器
|
||||
// registry.addInterceptor(new SecurityInterceptor());
|
||||
// 注册审计拦截器
|
||||
registry.addInterceptor(new AuditInterceptor(customProperties.getBasePackage()));
|
||||
// 注册Sa-Token拦截器 登录校验
|
||||
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
|
||||
.excludePathPatterns("/error", "/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**")
|
||||
.excludePathPatterns("/files/**")
|
||||
.excludePathPatterns("/doc.html", "/webjars/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置CORS跨越 需要实现 WebMvcConfigurer 接口
|
||||
* @return 过滤器
|
||||
*/
|
||||
@Bean
|
||||
@Order(-128)
|
||||
public CorsFilter corsFilter() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
// 允许所有来源
|
||||
config.addAllowedOrigin("*");
|
||||
// 允许所有请求头
|
||||
config.addAllowedHeader("*");
|
||||
// 允许所有请求方法
|
||||
config.addAllowedMethod("*");
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java [AuditInterceptor]
|
||||
public class AuditInterceptor implements HandlerInterceptor {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AuditInterceptor.class);
|
||||
|
||||
private final String basePackage;
|
||||
|
||||
public AuditInterceptor(String basePackage) {
|
||||
this.basePackage = basePackage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
// 如果是Http请求
|
||||
if (handler instanceof HandlerMethod hd) {
|
||||
request.setAttribute(Constants.AUDIT_TIME_FLAG, System.currentTimeMillis());
|
||||
// 判断该请求是否需要审计
|
||||
if (WebUtils.checkIsAuditPackages(hd.getBeanType().getPackage(), basePackage)) {
|
||||
handleAuditPackageRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
// true表示继续处理请求
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
// 这里我们不需要处理时间计算,所有的计算在 afterCompletion 中完成
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||
// 获取开始时间
|
||||
Long startTime = (Long) request.getAttribute(Constants.AUDIT_TIME_FLAG);
|
||||
if (startTime != null) {
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
logger.info("<AuditSummary> Request URL {} | Time Taken {} ms", request.getRequestURI(), duration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理审计包请求
|
||||
* @param request 请求
|
||||
*/
|
||||
private void handleAuditPackageRequest(HttpServletRequest request) {
|
||||
if (StringUtils.isEmpty(request.getQueryString())) {
|
||||
logger.info("<AuditSummary> {} {}", request.getMethod(), request.getRequestURI());
|
||||
} else {
|
||||
logger.info("<AuditSummary> {} {}?{}", request.getMethod(), request.getRequestURI(), WebUtils.format2UTF8(request.getQueryString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java [DefaultExceptionAdvice]
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class DefaultExceptionAdvice {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionAdvice.class);
|
||||
|
||||
/**
|
||||
* 处理SaToken权限错误
|
||||
* @param e 权限异常
|
||||
* @return 错误响应
|
||||
*/
|
||||
@ExceptionHandler(SaTokenException.class)
|
||||
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||
public ErrorResponse handleSaTokenException(SaTokenException e) {
|
||||
ErrorResponse response = new ErrorResponse(String.valueOf(e.getCode()), e.getMessage());
|
||||
logger.error("<{}> {}", response.getCode(), e.toString());
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数校验错误
|
||||
* @param e 校验异常
|
||||
* @return 错误响应
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ErrorResponse handleValidationException(MethodArgumentNotValidException e) {
|
||||
ErrorResponse response = new ErrorResponse(WebUtils.formatValidationException(e.getBindingResult().getFieldErrors()));
|
||||
logger.error("<{}> {}", response.getCode(), response.getMessage());
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理自定义异常错误
|
||||
* @param e 自定义异常
|
||||
* @return 错误响应
|
||||
*/
|
||||
@ExceptionHandler(AbstractException.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public ErrorResponse handleAbstractException(AbstractException e) {
|
||||
ErrorResponse response;
|
||||
if (e.getCode() == null || e.getCode().isEmpty()) {
|
||||
response = new ErrorResponse(e.getMessage());
|
||||
} else {
|
||||
response = new ErrorResponse(e.getCode(), e.getMessage());
|
||||
}
|
||||
logger.error(String.format("<%s> ", response.getCode()), e);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理默认异常错误
|
||||
* @param e 默认异常
|
||||
* @return 错误响应
|
||||
*/
|
||||
@ExceptionHandler
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public ErrorResponse handleDefaultException(Exception e) {
|
||||
ErrorResponse response = new ErrorResponse(e.getMessage());
|
||||
logger.error(String.format("<%s> ", response.getCode()), e);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java [AuditBodyAdvice]
|
||||
@ControllerAdvice
|
||||
@Import({CustomProperties.class})
|
||||
public class AuditBodyAdvice implements RequestBodyAdvice, ResponseBodyAdvice<Object> {
|
||||
@Resource
|
||||
private CustomProperties customProperties;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AuditBodyAdvice.class);
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
// 判断是否为需要审计的包
|
||||
String auditPackages = customProperties.getBasePackage();
|
||||
return WebUtils.checkIsAuditPackages(methodParameter.getDeclaringClass().getPackage(), auditPackages)
|
||||
&& AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
|
||||
return inputMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
// 入参结束
|
||||
try {
|
||||
String jsonBody = objectMapper.writeValueAsString(body);
|
||||
logger.info("<AuditRequest> {}", jsonBody);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.info("<AuditResponse> {}", body);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
// 判断是否为需要审计的包
|
||||
String auditPackages = customProperties.getBasePackage();
|
||||
return WebUtils.checkIsAuditPackages(returnType.getDeclaringClass().getPackage(), auditPackages)
|
||||
&& AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||
// 出参结束
|
||||
if (body != null && !(body instanceof ErrorResponse)) {
|
||||
try {
|
||||
String jsonBody = objectMapper.writeValueAsString(body);
|
||||
logger.info("<AuditResponse> {}", jsonBody);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.info("<AuditResponse> {}", body);
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```java [CustomProperties]
|
||||
/**
|
||||
* 用于将配置文件(如 application.properties 或 application.yml)中的属性值绑定到 Java 对象
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "web-starter")
|
||||
public class CustomProperties {
|
||||
// yml中web-starter下的base-package字段值
|
||||
private String basePackage = "";
|
||||
|
||||
public String getBasePackage() {
|
||||
return basePackage;
|
||||
}
|
||||
|
||||
public void setBasePackage(String basePackage) {
|
||||
this.basePackage = basePackage;
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
  `@ConfigurationProperties`用于将配置文件(如 application.properties 或 application.yml)中的属性值绑定到 Java 对象
|
||||
|
||||
::: tip
|
||||
SpringBoot MVC总体执行顺序:
|
||||
1. 进入Tomcat容器
|
||||
2. 进入Filter过滤器
|
||||
3. 进入Servlet容器
|
||||
4. 进入Interceptor拦截器
|
||||
5. 进入Controller控制器
|
||||
6. 进入AOP
|
||||
:::
|
||||
|
||||
::: tip
|
||||
请求和响应体拦截器
|
||||
RequestBodyAdvice, ResponseBodyAdvice主要发生在Controller执行前后:
|
||||
1. preHandle():请求处理前
|
||||
2. beforeBodyRead:请求体反序列化前
|
||||
3. @RequestBody:Controller方法参数绑定
|
||||
4. Controller:请求处理
|
||||
5. beforeBodyWrite():响应体序列化之前
|
||||
6. postHandle():请求处理后
|
||||
7. 视图渲染
|
||||
8. afterCompletion():请求结束
|
||||
:::
|
||||
|
||||
::: tip
|
||||
审计拦截器 可以实现请求日志打印等功能
|
||||
HandlerInterceptor 拦截器执行顺序:
|
||||
1. preHandle():请求处理前 按注册顺序依次执行。
|
||||
2. Controller:请求处理 请求到达Controller并被处理。
|
||||
3. postHandle():请求处理后,视图渲染前 按注册顺序逆序执行。
|
||||
4. afterCompletion():视图渲染后 按注册顺序逆序执行。
|
||||
:::
|
||||
|
||||
# 四、项目发布
|
||||
|
||||
## 4.1 发布到Git中
|
||||
1. 在Gitee/Github中创建工程,需要在工程中创建一个文件夹,例如repo,后续发布的文件要放在该文件夹下。
|
||||
2. 在maven的setting.xml中添加server和repository信息
|
||||
```xml
|
||||
<servers>
|
||||
<server>
|
||||
<id>gitee</id>
|
||||
<username>Cxx0822</username>
|
||||
<password>token</password>
|
||||
</server>
|
||||
</servers>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>gitee</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>central</id>
|
||||
<url>https://maven.aliyun.com/repository/central</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>gitee</id>
|
||||
<url>https://gitee.com/Cxx0822/springboot2-common/raw/master/repo</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>central</id>
|
||||
<url>https://maven.aliyun.com/repository/central</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<activeProfiles>
|
||||
<activeProfile>gitee</activeProfile>
|
||||
</activeProfiles>
|
||||
```
|
||||
|
||||
  注:这里要去掉mirror的阿里云镜像。
|
||||
|
||||
3. 在工程的根目录的pom.xml中添加发布配置:
|
||||
```xml
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>gitee</id>
|
||||
<name>springboot2-common</name>
|
||||
<url>file:D:/temp/maven</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
```
|
||||
|
||||
  注:gitee不支持通过deploy发布jar包,可以先发布到本地,再将文件复制到项目文件夹下,通过git push推送。
|
||||
|
||||
4. 将本地产生的发布文件上传至git仓库中。
|
||||
5. 其他项目引用:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-web</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-jdbc</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-logging</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
# 五、项目使用
|
||||
1. 将父工程改为starter-parent模块
|
||||
```xml
|
||||
<parent>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-parent</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
```
|
||||
2. 根据需要引入starter-web、starter-jdbc和starter-logging模块
|
||||
```xml
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-web</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-jdbc</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cxx</groupId>
|
||||
<artifactId>starter-logging</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
3. 打包模块时,需要添加spring-boot-maven-plugin
|
||||
```xml
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
```
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、启动流程
|
||||

|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、原生镜像
|
||||
  传统 Java 应用基于 JVM 运行,需要加载完整的类库和 JVM 运行时环境,导致启动时间长、内存占用高。而**原生镜像技术通过提前编译(AOT)将 Java 应用直接编译为本地机器码,无需 JVM 即可运行**,具有以下核心优势:
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、Bean概念
|
||||
## 1.1 定义
|
||||
  Spring bean是Spring框架在运行时管理的对象,Bean是一个由Spring IoC容器实例化、组装和管理的对象。
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、简介
|
||||
  Spring Boot Starter是一组预定义的依赖项集合,旨在简化Maven或Gradle等构建工具中的依赖管理。每个Starter都包含了实现特定功能所需的库和组件,以及相应的配置文件。开发者只需在项目中引入相应的Starter依赖,即可快速搭建起具备该功能的项目骨架。
|
||||
  Starter=依赖+自动配置+配置文件
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、IOC
|
||||
  Spring框架的核心是IOC(控制反转)容器,它负责管理应用程序中的对象(称为Bean)的创建、配置和组装。
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、基础概念
|
||||
  Spring MVC(Spring Model-View-Controller)是 Spring Framework 中的一部分,它是一个基于 **<font style="color:#DF2A3F;">请求驱动</font>** 的 Web 框架,主要用于构建 Web 应用程序,并且遵循 MVC(模型-视图-控制器) 设计模式。它将 Web 应用的业务逻辑、用户界面和请求处理分离,使得代码更加模块化和可维护。
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ SpringBoot 框架学习系列:
|
||||
- [SpringBoot Bean简介](/Web-Backend/SpringBoot/SpringBootBean)
|
||||
- [SpringBoot3原生镜像](/Web-Backend/SpringBoot/SpringBoot3-GraalVM)
|
||||
- [SpingBoot技巧](/Web-Backend/SpringBoot/SpingBoot-Skills)
|
||||
- [SpringBoot Common](/Web-Backend/SpringBoot/SpringBoot-Common)
|
||||
|
||||
### 🐍 FastAPI
|
||||
Python FastAPI 框架学习:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、网络请求阶段
|
||||
1. DNS 解析
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# JavaScript简介
|
||||
## JavaScript 实现
|
||||
  完整的`JavaScript`实现包含以下几个部分:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# npm
|
||||
  2010年随Node.js发布的npm,首次为JavaScript引入了标准的包管理系统。其核心创新是**package.json文件**,通过语义化版本规范定义了依赖声明标准。这一设计解决了手动管理依赖时的版本混乱问题,为模块化开发提供了基础设施。
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、Vite简介
|
||||
  Vite是新一代的前端构建工具,在尤雨溪开发Vue3.0的时候诞生。类似于Webpack+ Webpack-dev-server。
|
||||
  其主要利用浏览器**ESModule特性**导入组织代码,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。生产中利用Rollup作为打包工具,号称下一代的前端构建工具。
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、Vue3 整体架构图
|
||||
```mermaid
|
||||
graph TD
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<ArticleMetadata />
|
||||
|
||||
# 一、依赖管理
|
||||
  本项目会传递安装的依赖有:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user