Commit on 2025/04/24 周四 19:00:14.19

This commit is contained in:
zhangsan 2025-04-24 19:00:14 +08:00
parent 65fc5aa440
commit f6f3097112
7 changed files with 387 additions and 124 deletions

35
科研/Mesa仿真.md Normal file
View File

@ -0,0 +1,35 @@
# Mesa仿真
## 配置环境
**requirements.txt**
```text
mesa[rec] # 包含 networkx、matplotlib、ipywidgets、solara 等推荐依赖
jupyterlab
numpy
pandas
```
**Conda 命令行**
```bash
# 1) 添加 conda-forge 通道并设为最高优先级
conda config --add channels conda-forge
conda config --set channel_priority strict
# 2) 创建并激活新环境(这里以 python 3.11 为例)
conda create -n mesa-env python=3.11 -y
conda activate mesa-env
# 3a) 通过 pip 安装(使用上面的 requirements.txt
pip install -r requirements.txt
# 或者 3b) 纯 Conda 安装等价包(推荐所有包都从 conda-forge
conda install \
mesa=3.1.5 networkx matplotlib ipywidgets solara \
numpy pandas jupyterlab \
-c conda-forge
```

View File

@ -1,34 +1,47 @@
您发现了符号不一致的问题!确实,这里需要统一符号表示。根据论文和您之前的笔记,正确的对应关系如下: 分布式计算和集中式计算是完全等价的。将分布式算法中的本地观测
$$
b_i(k) = \sum_j a_{ij} x_j(k)
$$
代入瑞利商公式
$$
y(k) = \frac{\sum_i x_i(k) b_i(k)}{\sum_i x_i(k)^2}
$$
即可得到集中式计算的Rayleigh商形式
$$
\frac{x(k)^T (A x(k))}{x(k)^T x(k)},
$$
这与特征值估计的幂迭代公式
$$
\lambda_{\max} \approx \frac{x^T A x}{x^T x}
$$
完全一致。
--- ---
### **修正后的公式** ### 代数示例
1. **瑞利商计算**对应论文式4-51
- 所有节点交换的是**本地滤波结果** $\hat{b}_{i,k|k}$即UKF更新后的状态估计值而非中间变量$b_i(k)$。
- 正确的全局状态计算应为:
$$
y(k) = \frac{\sum_{i=1}^N x_i(k) \hat{b}_{i,k|k}}{\sum_{i=1}^N x_i^2(k)}
$$
- **物理意义**$x_i(k)$是节点$i$的状态分量(用于正交化),$\hat{b}_{i,k|k}$是UKF更新的本地估计值。
2. **正交化**对应论文式4-52 考虑一个简单的2×2矩阵
- 更新的是**状态分量** $x_i(k+1)$,其归一化分母应为所有节点的$\hat{b}_{i,k|k}$的2-范数: $$
$$ A = \begin{pmatrix}2 & 1\\1 & 2\end{pmatrix}, \quad x = \begin{pmatrix}2\\1\end{pmatrix}.
x_i(k+1) = \frac{\hat{b}_{i,k|k}}{\|\hat{b}(k)\|_2}, \quad \text{其中} \quad \|\hat{b}(k)\|_2 = \sqrt{\sum_{i=1}^N \hat{b}_{i,k|k}^2} $$
$$
--- 1. **集中式计算**
$$
\lambda \approx \frac{x^T A x}{x^T x}
= \frac{\begin{pmatrix}2 & 1\end{pmatrix} \begin{pmatrix}5\\4\end{pmatrix}}{2^2 + 1^2}
= \frac{10 + 4}{5}
= \frac{14}{5} = 2.8.
$$
### **符号一致性说明** 2. **分布式计算**
- **$\hat{b}_{i,k|k}$**:节点$i$在时刻$k$的UKF后验估计值即滤波结果 各节点分别计算本地观测值
- **$x_i(k)$**:节点$i$的状态分量(用于分布式正交化,与$\hat{b}_{i,k|k}$同步更新)。 $$
- **$b_i(k)$**在论文中可能被用作中间变量如式4-40的幂迭代状态但最终滤波输出统一为$\hat{b}_{i,k|k}$。 b_1 = (2 \cdot 2 + 1 \cdot 1) = 5, \quad b_2 = (1 \cdot 2 + 2 \cdot 1) = 4,
$$
然后通过全网共识计算
$$
y = \frac{2 \cdot 5 + 1 \cdot 4}{2^2 + 1^2}
= \frac{14}{5} = 2.8.
$$
--- 两种方法得到的结果完全相同。
### **修正后的流程**
1. **UKF更新**:每个节点计算$\hat{b}_{i,k|k}$原笔记Step 4输出
2. **一致性协议**:节点交换$\hat{b}_{i,k|k}$,计算瑞利商$y(k)$。
3. **正交化**:用$\hat{b}_{i,k|k}$更新$x_i(k+1)$,确保状态分量正交性。
这样既符合论文的分布式滤波逻辑,又保持了符号一致性。是否需要进一步解释某一步骤?

View File

@ -17,7 +17,7 @@ $$
在所有概率分布里,只有指数分布 在所有概率分布里,只有指数分布
$$ $$
P(T>t) = e^{-\lambda t} P(T>t) = e^{-\lambda t}
$$ $$
具有这种“无记忆性”特征: 具有这种“无记忆性”特征:
@ -43,7 +43,7 @@ $$
- 从断开0到连通1的等待时间 $T_{01} \sim \text{Exp}(\lambda_{01})$ - 从断开0到连通1的等待时间 $T_{01} \sim \text{Exp}(\lambda_{01})$
- 从连通1到断开0的等待时间 $T_{10} \sim \text{Exp}(\lambda_{10})$ - 从连通1到断开0的等待时间 $T_{10} \sim \text{Exp}(\lambda_{10})$
其中,$\lambda_{01}$ 和 $\lambda_{10}$ 为转移速率,表示单位时间内事件(转移)发生的**平均次数** 其中,**$\lambda_{01}$ 和 $\lambda_{10}$ 为转移速率**,表示单位时间内事件(转移)发生的**平均次数**
#### **2.推导单条链路的连通概率** #### **2.推导单条链路的连通概率**
@ -302,6 +302,8 @@ $$
## 网络特征谱参数的估算 ## 网络特征谱参数的估算
由于邻接矩阵不能保证半正定性,因此会产生幂迭代估算过程不能收敛的问题。需构造$A^T A$
### 基于奇异值分解改进幂迭代估算(集中式) ### 基于奇异值分解改进幂迭代估算(集中式)
**输入**:矩阵 $B = A^T A$,目标特征值数量 $k$,收敛阈值 $\delta$ **输入**:矩阵 $B = A^T A$,目标特征值数量 $k$,收敛阈值 $\delta$
@ -359,6 +361,63 @@ $$
### 瑞利商公式
1. 集中式:
$$
y(k)= \frac{x(k)^T A x(k)}{x(k)^T x(k)}
$$
2. 分布式一致性计算:
$$
y(k) = \frac{\sum_{i=1}^N x_i(k) b_i(k)}{\sum_{i=1}^N x_i^2(k)}
$$
其中
$$
b_i(k) = \sum_j a_{ij} x_j(k)
$$
**两者是等价的:**
考虑一个简单的2×2矩阵
$$
A = \begin{pmatrix}2 & 1\\1 & 2\end{pmatrix}, \quad x = \begin{pmatrix}2\\1\end{pmatrix}.
$$
1. **集中式计算**
$$
y= \frac{x^T A x}{x^T x}
= \frac{\begin{pmatrix}2 & 1\end{pmatrix} \begin{pmatrix}5\\4\end{pmatrix}}{2^2 + 1^2}
= \frac{10 + 4}{5}
= \frac{14}{5} = 2.8.
$$
2. **分布式计算**
各节点分别计算本地观测值
节点1的计算
$$
b_1 = a_{11}x_1 + a_{12}x_2 = 2 \cdot 2 + 1 \cdot 1 = 5.
$$
节点2的计算
$$
b_2 = a_{21}x_1 + a_{22}x_2 = 1 \cdot 2 + 2 \cdot 1 = 4.
$$
然后通过全网共识计算
$$
y = \frac{2 \cdot 5 + 1 \cdot 4}{2^2 + 1^2}
= \frac{14}{5} = 2.8.
$$
### 主要符号表 ### 主要符号表
| 符号 | 类型 | 含义 | 存储/计算位置 | | 符号 | 类型 | 含义 | 存储/计算位置 |
@ -402,7 +461,7 @@ $$
- **Repeat** - **Repeat**
a. **第一轮通信(计算$z=Av$** a. **第一轮通信(计算$z=Av$**
$$ $$
z_j^{(t)} = \sum_{k \in 𝒩_j} a_{jk} v_{n,k}^{(t)} \quad \text{(邻居交换$v_{n,k}^{(t)}$)} z_j^{(t)} = \sum_{k \in 𝒩_j} a_{jk} v_{n,k}^{(t)} \quad \text{(邻居交换$v_{n,k}^{(t)}$)}
$$ $$
b. **第二轮通信(计算$y=A^T z$** b. **第二轮通信(计算$y=A^T z$**
$$ $$
@ -422,7 +481,7 @@ $$
$$ $$
f. **终止条件** f. **终止条件**
$$ $$
\text{If } \frac{|\lambda^{(t)} - \lambda^{(t-1)}|}{|\lambda^{(t)}|} < \delta \text{ then break} \text{If } \frac{|\lambda^{(t)} - \lambda^{(t-1)}|}{|\lambda^{(t)}|} < \delta \text{ then break}
$$ $$
3. **保存结果** 3. **保存结果**
@ -434,6 +493,8 @@ $$
### 分布式计算左奇异向量$u_{n,j}$ ### 分布式计算左奇异向量$u_{n,j}$
对于邻接矩阵 $A \in \mathbb{R}^{N \times N}$,其奇异值分解为: 对于邻接矩阵 $A \in \mathbb{R}^{N \times N}$,其奇异值分解为:
@ -595,8 +656,8 @@ $$
#### **符号说明** #### **符号说明**
- **$i$**: 节点索引,$N$ 为总节点数 - **$i$**: 节点索引,$N$ 为总节点数
- **$x_i(k)$**: 节点 $i$ 在时刻 $k$ 的状态分量 (特征向量$x$) - **$x_i(k)$**: 节点 $i$ 在时刻 $k$ 的状态分量 ($x$)
- **$b_i(k)$**: 节点 $i$ 的本地状态估计值 ($A^TAx$) - **$b_i(k)$**: 节点 $i$ 的本地状态估计值 (相当于$Ax$
- **$a_{ij}$**: 邻接矩阵元素(链路权重) - **$a_{ij}$**: 邻接矩阵元素(链路权重)
- **$Q_k, R_k$**: 过程噪声与观测噪声协方差 - **$Q_k, R_k$**: 过程噪声与观测噪声协方差
- **$\mathcal{X}_{i,j}$**: 节点 $i$ 的第 $j$ 个 Sigma 点 - **$\mathcal{X}_{i,j}$**: 节点 $i$ 的第 $j$ 个 Sigma 点
@ -666,12 +727,16 @@ $$
1. **邻居状态融合** 1. **邻居状态融合**
- 节点 $i$ 从邻居 $j$ 获取状态 $x_j(k)$,生成观测值: - 节点 $ i $ 从邻居 $ j $ 获取其本地观测值 $ b_{j,\text{local}}(k) $
$$
b_{j,\text{local}}(k) = \sum_{l=1}^N a_{jl} x_l(k) \quad \text{(节点 $ j $ 对邻居状态的加权融合)}
$$
- 节点 $ i $ 综合邻居信息生成自身观测:
$$ $$
b_i^H(k) = \sum_{j=1}^N a_{ji} b_{0,j}(k) + r_k, \quad b_{0,j}(k) = \sum_{j=1}^N a_{ij} x_j(k) b_i^H(k) = \sum_{j=1}^N a_{ji} b_{j,\text{local}}(k) + r_k
$$ $$
- **注**$ r_k $ 为通信噪声,反映信息传输误差(如延时、丢包)。
- **$r_k$** 为观测噪声 ,来自分布式通信中的信息传输误差(如延时、丢包)
--- ---
@ -725,7 +790,7 @@ $$
2. **正交化** 2. **正交化**
- 更新本地状态分量: - 更新本地状态分量(相当于幂迭代$x=Ax$再归一化)
$$ $$
x_i(k+1) = \frac{\hat{b}_{i,k|k}}{\|\hat{b}(k)\|_2} x_i(k+1) = \frac{\hat{b}_{i,k|k}}{\|\hat{b}(k)\|_2}
$$ $$

View File

@ -358,6 +358,20 @@ public class SpringDataRedisTest {
} }
``` ```
**对于非String类型**
**写入时序列化**
- 调用 `opsForValue().set(key, value)` 时,`value`(比如 `List<DishVO>`)会先走一遍 **ValueSerializer**
- 默认是 `JdkSerializationRedisSerializer`,它用 Java 的 `ObjectOutputStream` 把整个对象图打包成一个 byte[]。
- 这个 byte[] 直接就存成了 Redis 的 String 值。
**读取时反序列化**
- 调用 `opsForValue().get(key)`RedisTemplate 拿回那段 byte[],再用同一个 JDK 序列化器(`ObjectInputStream`)把它变回 `List<DishVO>`
**哈希测试** **哈希测试**
```java ```java

View File

@ -820,10 +820,20 @@ curl -v -X POST "https://yourdomain/api/resources/${ENCODED_PATH}?override=true"
--data-binary "@${FILE_PATH}" --data-binary "@${FILE_PATH}"
``` ```
**拼接下载url** **获取分享链接url**
```text ```text
String downloaded_url=yourdomain + "/api/raw/" + ${ENCODED_PATH}; # 假设已经执行过 /api/login 并把 JWT 保存在环境变量 TOKEN
# 同样复用之前算好的 ENCODED_PATH 和 REMOTE_PATH
# 调用分享接口注意POSTbody 为空 JSON "{}"
curl -s -X POST "https://yourdomain/api/share/${ENCODED_PATH}" \
-H "X-Auth: ${TOKEN}" \
-H "Cookie: auth=${TOKEN}" \
-H "Content-Type: text/plain;charset=UTF-8" \
-d '{}' \
| jq -r '.hash' \
| xargs -I{} printf "https://yourdomain/api/public/dl/%s/%s\n" {} "${REMOTE_PATH}"
``` ```

View File

@ -80,6 +80,45 @@ public static int compare(int x, int y) {
### 位运算
按位与 `&`:只有两个对应位都为 1 时,结果位才为 1。
```java
int a = 5; // 0101₂
int b = 3; // 0011₂
int c = a & b; // 0001₂ = 1
System.out.println(c); // 输出 1
```
按位或 `|`: 只要两个对应位有一个为 1结果位就为 1。
```java
int a = 5; // 0101₂
int b = 3; // 0011₂
int c = a | b; // 0111₂ = 7
System.out.println(c); // 输出 7
```
按位异或 `^`: 两个对应位不同则为 1相同则为 0。
```java
int a = 5; // 0101₂
int b = 3; // 0011₂
int c = a ^ b; // 0110₂ = 6
System.out.println(c); // 输出 6
```
左移 `<<`: 整体二进制左移 N 位,右侧补 0相当于乘以 2ⁿ。
```java
int a = 3; // 0011₂
int b = a << 2; // 1100 = 12
System.out.println(b); // 输出 12
```
## 常用数据结构 ## 常用数据结构
### String ### String
@ -1097,6 +1136,10 @@ public class ArraySortExample {
} }
``` ```
`Arrays.sort(nums, i + 1, n);` 等价于把 `nums[i+1]``nums[n-1]` 这段做升序排序。
自定义降序: 自定义降序:
@ -1110,9 +1153,11 @@ public class DescendingSortExample {
Integer[] arr = {5, 2, 9, 1, 5, 6}; Integer[] arr = {5, 2, 9, 1, 5, 6};
// 使用Comparator进行降序排序使用lambda表达式 // 使用Comparator进行降序排序使用lambda表达式
Arrays.sort(arr, (a, b) -> b - a); Arrays.sort(arr, (a, b) -> Integer.compare(b, a));
// 或者使用Collections.reverseOrder()也可以: // 或者使用Collections.reverseOrder()也可以:
// Arrays.sort(arr, Collections.reverseOrder());
// 对下标 [1, 4) 的区间,也就是 {2,9,1},按降序排序
Arrays.sort(arr, 1, 4, Collections.reverseOrder());
// 输出排序后的数组 // 输出排序后的数组
System.out.println(Arrays.toString(arr)); System.out.println(Arrays.toString(arr));

View File

@ -708,46 +708,38 @@ public class FileBrowserUtil {
/** /**
* —— 第一步:登录拿 token —— * —— 第一步:登录拿 token ——
* 调用 /api/login 接口,返回原始的 JWT token 字符串 * 调用 /api/login 接口,返回纯 JWT 字符串,或 {"token":"..."} 结构
* curl -X POST "https://fshare.bitday.top/api/login" \
* -H "Content-Type: application/json" \
* -d '{
* "username":"admin",
* "password":"asdf14789"
* }'
* 返回值: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…
*/ */
public String login() throws IOException { public String login() throws IOException {
String url = domain + "/api/login"; String url = domain + "/api/login";
// 创建 HttpClient 实例 try (CloseableHttpClient client = HttpClients.createDefault()) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpPost post = new HttpPost(url);
// 构造 POST 请求 post.setHeader("Content-Type", "application/json");
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/json");
// 构造 JSON body // 构造登录参数
JSONObject json = new JSONObject(); JSONObject cred = new JSONObject();
json.put("username", username); cred.put("username", username);
json.put("password", password); cred.put("password", password);
post.setEntity(new StringEntity(cred.toString(), StandardCharsets.UTF_8));
// 设置请求体 try (CloseableHttpResponse resp = client.execute(post)) {
StringEntity requestEntity = new StringEntity(json.toString(), StandardCharsets.UTF_8); int status = resp.getStatusLine().getStatusCode();
requestEntity.setContentType("application/json"); String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8).trim();
httpPost.setEntity(requestEntity);
// 发送请求并处理响应 if (status >= 200 && status < 300) {
try (CloseableHttpResponse response = httpClient.execute(httpPost)) { // 如果返回 JSON 对象,则解析出 token 字段
int statusCode = response.getStatusLine().getStatusCode(); if (body.startsWith("{") && body.endsWith("}")) {
if (statusCode >= 200 && statusCode < 300) { JSONObject obj = JSONObject.parseObject(body);
HttpEntity respEntity = response.getEntity(); String token = obj.getString("token");
String body = EntityUtils.toString(respEntity, StandardCharsets.UTF_8); log.info("登录成功token={}", token);
log.info("token:{}",body); return token;
// 返回原始返回值(假设就是 JWT token 字符串) }
// 否则直接当成原始 JWT 返回
log.info("登录成功token={}", body);
return body; return body;
} else { } else {
log.error("Login failed, HTTP status code: " + statusCode); log.error("Login failed: HTTP {} - {}", status, body);
return ""; throw new IOException("Login failed: HTTP " + status);
} }
} }
} }
@ -755,50 +747,98 @@ public class FileBrowserUtil {
/** /**
* —— 第二步:上传文件 —— * —— 第二步:上传文件 ——
+ * curl -v -X POST \ * POST {domain}/api/resources/{encodedPath}?override=true
+ * "$DOMAIN/api/resources/$REMOTE_PATH?override=true" \ // 服务器上相对路径 * Header: X-Auth: token
+ * -H "X-Auth: $TOKEN" \
+ * -H "Content-Type: image/jpeg" \ // 根据文件类型替换
+ * --data-binary "@/path/to/local/photo.jpg" // 以 raw body 方式上传
*/ */
public String uploadFile(byte[] fileBytes, String fileName) throws IOException {
public String uploadAndGetUrl(byte[] fileBytes, String fileName) throws IOException {
// 1. 登录拿 token
String token = login(); String token = login();
// 2. 确定远端存储路径和编码(保留斜杠)
String remotePath = "store/" + fileName; String remotePath = "store/" + fileName;
String encodedPath = URLEncoder.encode(remotePath, StandardCharsets.UTF_8.toString()) String encodedPath = URLEncoder
.encode(remotePath, StandardCharsets.UTF_8)
.replace("%2F", "/"); .replace("%2F", "/");
// 3. 根据文件名猜 MIME 类型fallback 到 application/octet-stream // 根据后缀猜 MIME 类型
String mimeType = URLConnection.guessContentTypeFromName(fileName); String mimeType = URLConnection.guessContentTypeFromName(fileName);
if (mimeType == null) { if (mimeType == null) {
mimeType = "application/octet-stream"; mimeType = "application/octet-stream";
} }
// 4. 构造上传 URL
String uploadUrl = domain + "/api/resources/" + encodedPath + "?override=true"; String uploadUrl = domain + "/api/resources/" + encodedPath + "?override=true";
// 5. 执行 Multipart upload
try (CloseableHttpClient client = HttpClients.createDefault()) { try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpPost post = new HttpPost(uploadUrl); HttpPost post = new HttpPost(uploadUrl);
post.setHeader("X-Auth", token); post.setHeader("X-Auth", token);
post.setEntity(new ByteArrayEntity(fileBytes, ContentType.create(mimeType))); post.setEntity(new ByteArrayEntity(fileBytes, ContentType.create(mimeType)));
try (CloseableHttpResponse resp = client.execute(post)) { try (CloseableHttpResponse resp = client.execute(post)) {
int status = resp.getStatusLine().getStatusCode(); int status = resp.getStatusLine().getStatusCode();
String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8); String respBody = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8);
if (status < 200 || status >= 300) { if (status < 200 || status >= 300) {
log.error("文件上传失败: HTTP {}{}", status, body); log.error("文件上传失败: HTTP {} - {}", status, respBody);
return ""; throw new IOException("Upload failed: HTTP " + status);
} }
log.info("文件上传成功remotePath={}", remotePath);
return remotePath;
} }
} }
}
// 6. 拼接 raw 下载链接并返回 /**
String downloadUrl = domain + "/api/raw/" + encodedPath; * 第三步:生成公开分享链接
log.info("文件下载链接:{}", downloadUrl); * 模拟浏览器的 POST /api/share/{encodedPath} 请求body 为 "{}"
return downloadUrl; */
public String createShareLink(String remotePath) throws IOException {
String token = login();
// URL encode 并保留斜杠
String encodedPath = URLEncoder
.encode(remotePath, StandardCharsets.UTF_8)
.replace("%2F", "/");
String shareUrl = domain + "/api/share/" + encodedPath;
log.info("准备创建分享链接POST {}", shareUrl);
try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpPost post = new HttpPost(shareUrl);
post.setHeader("Cookie", "auth=" + token);
post.setHeader("X-Auth", token);
post.setHeader("Content-Type", "text/plain;charset=UTF-8");
post.setEntity(new StringEntity("{}", StandardCharsets.UTF_8));
try (CloseableHttpResponse resp = client.execute(post)) {
int status = resp.getStatusLine().getStatusCode();
String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8).trim();
if (status < 200 || status >= 300) {
log.error("创建分享失败 HTTP {} - {}", status, body);
throw new IOException("Share failed: HTTP " + status);
}
// ========= 这里改为先检测是对象还是数组 =========
JSONObject json;
if (body.startsWith("[")) {
// 如果真返回数组(老版本可能是 [{...}]
json = JSONArray.parseArray(body).getJSONObject(0);
} else if (body.startsWith("{")) {
// 当前版本直接返回对象
json = JSONObject.parseObject(body);
} else {
throw new IOException("Unexpected share response: " + body);
}
String hash = json.getString("hash");
String publicUrl = domain + "/api/public/dl/" + hash + "/" + remotePath;
log.info("创建分享链接成功:{}", publicUrl);
return publicUrl;
}
}
}
/**
* —— 整合示例:上传并立即返回分享链接 ——
*/
public String uploadAndGetUrl(byte[] fileBytes, String fileName) throws IOException {
String remotePath = uploadFile(fileBytes, fileName);
return createShareLink(remotePath);
} }
} }
``` ```
@ -1407,7 +1447,9 @@ Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要
| @CachePut | 将方法的返回值**放**到缓存中 | | @CachePut | 将方法的返回值**放**到缓存中 |
| @CacheEvict | 将一条或多条数据从缓存中**删除** | | @CacheEvict | 将一条或多条数据从缓存中**删除** |
**@CachePut 说明:**
**1@CachePut 说明:**
作用: 将方法返回值,放入缓存 作用: 将方法返回值,放入缓存
@ -1421,46 +1463,50 @@ Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要
在Redis中并没有直接的“缓存名”概念而是通过键key来访问数据。Spring Cache通过`cacheNames`属性来模拟不同的“缓存区”,实际上这是通过将这些名称作为键的一部分来实现的。例如,如果你有一个缓存名为 `userCache`,那么所有相关的缓存条目的键可能以 `"userCache::"` 开头。 在Redis中并没有直接的“缓存名”概念而是通过键key来访问数据。Spring Cache通过`cacheNames`属性来模拟不同的“缓存区”,实际上这是通过将这些名称作为键的一部分来实现的。例如,如果你有一个缓存名为 `userCache`,那么所有相关的缓存条目的键可能以 `"userCache::"` 开头。
**在save方法上加注解@CachePut**
```java ```java
@PostMapping @PostMapping
@CachePut(value = "userCache", key = "#user.id")//key的生成userCache::1 @CachePut(value = "userCache", key = "#user.id")//key的生成userCache::1
public User save(@RequestBody User user){ public User save(@RequestBody User user){
userMapper.insert(user); userMapper.insert(user);
return user; return user;
} }
``` ```
**说明:**key的写法如下 **说明:**key的写法如下
#user.id : #user指的是方法形参的名称, id指的是user的id属性 , 也就是使用user的id属性作为key ; #user.id : #user指的是**方法形参**的名称, id指的是user的id属性 , 也就是使用user的id属性作为key ;
#result.id : #result代表方法返回值,该表达式 代表以返回对象的id属性作为key #result.id : #result代表方法返回值,该表达式 代表以返回对象的id属性作为key
**@Cacheable 说明:** **2@Cacheable 说明:**
作用: 在方法执行前spring先查看缓存中是否有**指定的key的**数据如果有数据则直接返回缓存数据不执行后续sql操作若没有数据调用方法并将方法返回值放到缓存中。 作用: 在方法执行前spring先查看缓存中是否有**指定的key的**数据如果有数据则直接返回缓存数据不执行后续sql操作若没有数据调用方法并将方法返回值放到缓存中。
所以,@Cacheable(cacheNames = "userCache",key="#id")中的#id表示的是函数形参中的id而不能是返回值中的user.id **在getById上加注解@Cacheable**
```java ```java
@GetMapping @GetMapping
@Cacheable(cacheNames = "userCache",key="#id") @Cacheable(cacheNames = "userCache",key="#id")
public User getById(Long id){ public User getById(Long id){
User user = userMapper.getById(id); User user = userMapper.getById(id);
return user; return user;
} }
``` ```
**@CacheEvict 说明:**
作用: 清理指定缓存
**3@CacheEvict 说明:**
作用: 清理指定缓存
**在 delete 方法上加注解@CacheEvict**
```java ```java
@DeleteMapping @DeleteMapping
@CacheEvict(cacheNames = "userCache",key = "#id")//删除某个key对应的缓存数据 @CacheEvict(cacheNames = "userCache",key = "#id")//删除某个key对应的缓存数据
public void deleteById(Long id){ public void deleteById(Long id){
userMapper.deleteById(id); userMapper.deleteById(id);
@ -1475,23 +1521,52 @@ Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要
## 微信支付 **总结:**新增数据的时候->添加缓存@CachePut ;
小程序支付 查询的时候->判断有无缓存@Cacheable;
https://pay.weixin.qq.com/static/product/product_index.shtml 删除的时候->删除缓存@CacheEvict
![image-20221214223910840](https://pic.bitday.top/i/2025/03/19/u7z7u7-2.png) Spring Cache是经典缓存的上位替代
注意,如果缓存的是套餐分类,即一个套餐分类中含有多个套餐,那么在新增套餐的时候,需要清除相应的套餐分类缓存,因为当你新增一个属于分类 5 的套餐时,原来缓存里那份「分类 5 的列表」已经不再完整──它少了新加的那个套餐。
但是如果缓存的就是套餐本身,新增套餐的时候就可以直接缓存套餐。不要混淆两者!
**5.商户系统调用微信后台:** ## 下单支付
**下单**
<img src="https://pic.bitday.top/i/2025/04/24/sm31f9-0.png" alt="image-20221214200913654" style="zoom:50%;" /><img src="https://pic.bitday.top/i/2025/04/24/snaksp-0.png" alt="image-20221214200959943" style="zoom:50%;" />
| 表名 | 含义 | 说明 |
| ------------ | ---------- | ------------------------------------------------------------ |
| orders | 订单表 | 主要存储订单的基本信息(如: 订单号、状态、金额、支付方式、下单用户、收件地址等) |
| order_detail | 订单明细表 | 主要存储订单详情信息(如: 该订单关联的套餐及菜品的信息) |
### 微信支付
官方文档https://pay.weixin.qq.com/static/product/product_index.shtml
<img src="https://pic.bitday.top/i/2025/03/19/u7z7u7-2.png" alt="image-20221214223910840" style="zoom:80%;" />
**商户系统调用微信后台:**
**JSAPI下单**商户系统调用该接口在微信支付服务后台生成预支付交易单(对应时序图的第5步)
![image-20221214224409174](https://pic.bitday.top/i/2025/03/19/u7zxts-2.png) ![image-20221214224409174](https://pic.bitday.top/i/2025/03/19/u7zxts-2.png)
**10.用户调起微信支付** **微信小程序调起支付:**
通过JSAPI下单接口获取到发起支付的必要参数prepay_id然后使用微信支付提供的小程序方法调起小程序支付(对应时序图的第10步)
![image-20221214224551220](https://pic.bitday.top/i/2025/03/19/u7zlem-2.png) ![image-20221214224551220](https://pic.bitday.top/i/2025/03/19/u7zlem-2.png)
@ -1507,8 +1582,6 @@ https://pay.weixin.qq.com/static/product/product_index.shtml
1)下载地址https://dashboard.cpolar.com/get-started 1)下载地址https://dashboard.cpolar.com/get-started
**2). cpolar指定authtoken** **2). cpolar指定authtoken**
复制authtoken 复制authtoken
@ -1537,6 +1610,14 @@ cpolar.exe http 8080
**原理:**
1. 客户端向 cpolar 的中转节点发起 **出站**outbound连接完成身份认证authtoken并在连接上报出要映射的本地端口比如 HTTP 的 8080
2. 中转节点分配一个公网端点如abcd1234.cpolar.com
3. **外部用户** 访问 `http://abcd1234.cpolar.com`,落到 cpolar 的中转节点,中转节点 **通过先前建立好的持久隧道**,把流量转发到你本地运行的客户端。
## Spring Task ## Spring Task
**Spring Task** 是Spring框架提供的任务调度工具可以按照约定的时间自动执行某个代码逻辑。 **Spring Task** 是Spring框架提供的任务调度工具可以按照约定的时间自动执行某个代码逻辑。