Commit on 2025/07/18 周五 21:09:59.19

This commit is contained in:
zhangsan 2025-07-18 21:09:59 +08:00
parent 759551ffa0
commit 59de4f24a6
7 changed files with 462 additions and 54 deletions

View File

@ -412,9 +412,9 @@ MyInterface obj = () -> {
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:**一个参数无需定义圆括号**,但无参数或多个参数需要定义圆括号。 可选的参数圆括号()**一个参数无需定义圆括号**,但无参数或多个参数需要定义圆括号。
可选的大括号:如果主体**只有一个语句,可以不使用大括号**。 可选的大括号{}:如果主体**只有一个语句,可以不使用大括号**。
可选的返回关键字如果主体只有一个表达式返回值则编译器会自动返回值使用大括号需显示retrun如果函数是void则不需要返回值。 可选的返回关键字如果主体只有一个表达式返回值则编译器会自动返回值使用大括号需显示retrun如果函数是void则不需要返回值。
@ -480,15 +480,12 @@ public interface Comparator<T> {
``` ```
```java ```java
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class Main { public class Main {
public static void main(String[] args) { public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Adam", "Dana"); List<String> names = Arrays.asList("John", "Jane", "Adam", "Dana");
// 使用Lambda表达式排序 // 使用Lambda表达式排序
//public static <T> void sort(List<T> list, Comparator<? super T> c)
Collections.sort(names, (a, b) -> a.compareTo(b)); Collections.sort(names, (a, b) -> a.compareTo(b));
// 输出排序结果 // 输出排序结果

View File

@ -396,3 +396,27 @@ target/
打包失败的把test步骤去掉 打包失败的把test步骤去掉
![image-20250514125309214](https://pic.bitday.top/i/2025/05/14/kq2fpz-0.png) ![image-20250514125309214](https://pic.bitday.top/i/2025/05/14/kq2fpz-0.png)
### Maven镜像加速
maven构建依赖可能比较慢需创建 `.mvn/settings.xml` (这个提速非常多!务必添加)
```xml
<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0
https://maven.apache.org/xsd/settings-1.1.0.xsd">
<mirrors>
<mirror>
<id>aliyun</id>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/repository/public</url>
<mirrorOf>central,apache.snapshots</mirrorOf>
</mirror>
</mirrors>
</settings>
```
使用阿里云镜像加速,`.mvn` 放在项目根目录下

View File

@ -498,7 +498,7 @@ Set<String> set1 = new HashSet<>(Arrays.asList(wordList)); //构造器直接初
`remove()` / `poll()` `remove()` / `poll()`
- 功能:移除并返回队首元素。 - 功能:移除并返回**队首**元素。
- 时间复杂度:`O(log n)` - 时间复杂度:`O(log n)`
- 区别 - 区别
- `remove`:队列为空时抛出异常。 - `remove`:队列为空时抛出异常。
@ -506,7 +506,7 @@ Set<String> set1 = new HashSet<>(Arrays.asList(wordList)); //构造器直接初
`element()` / `peek()` `element()` / `peek()`
- 功能:查看队首元素,但不移除。 - 功能:查看**队首**元素,但不移除。
- 时间复杂度:`O(1)` - 时间复杂度:`O(1)`
- 区别 - 区别
- `element`:队列为空时抛出异常。 - `element`:队列为空时抛出异常。
@ -1130,16 +1130,13 @@ public class Main {
快速排序是一种基于“分治”思想的排序算法通过选定一个“枢轴元素pivot将数组划分为左右两个子区间左边都小于等于 pivot右边都大于等于 pivot然后对这两个子区间递归排序最终使整个数组有序。 快速排序是一种基于“分治”思想的排序算法通过选定一个“枢轴元素pivot将数组划分为左右两个子区间左边都小于等于 pivot右边都大于等于 pivot然后对这两个子区间递归排序最终使整个数组有序。
```java ```java
public class QuickSortWithSwap { public class QuickSort {
// 交换数组中两个元素的位置 /**
private static void swap(int[] arr, int i, int j) { * 对数组 arr 在下标 low 到 high 范围内进行快速排序
int temp = arr[i]; * 使用“挖坑填坑”方式,不再调用 swap 方法
arr[i] = arr[j]; */
arr[j] = temp; public static void quickSort(int[] arr, int low, int high) {
}
private static void quickSort(int[] arr, int low, int high) {
if (low < high) { if (low < high) {
int pivotPos = partition(arr, low, high); // 划分 int pivotPos = partition(arr, low, high); // 划分
quickSort(arr, low, pivotPos - 1); // 递归排序左子表 quickSort(arr, low, pivotPos - 1); // 递归排序左子表
@ -1147,40 +1144,96 @@ public class QuickSortWithSwap {
} }
} }
/**
* 划分函数:以 arr[low] 作为枢轴,通过“挖坑—填坑”将小于枢轴的元素移到左边,
* 大于枢轴的元素移到右边,最后返回枢轴的最终下标
*/
private static int partition(int[] arr, int low, int high) { private static int partition(int[] arr, int low, int high) {
int pivot = arr[low]; // 选取第一个元素作为枢轴 int pivot = arr[low]; // 先保存枢轴
int left = low; // 左指针 int left = low, right = high;
int right = high; // 右指针
while (left < right) { while (left < right) {
// 从右向左找第一个小于枢轴的元素 // 从右向左找第一个小于 pivot 的元素,填到 left 这个“坑”里
while (left < right && arr[right] >= pivot) { while (left < right && arr[right] >= pivot) {
right--; right--;
} }
// 从左向右找第一个大于枢轴的元素 arr[left] = arr[right];
// 从左向右找第一个大于 pivot 的元素,填到 right 这个“坑”里
while (left < right && arr[left] <= pivot) { while (left < right && arr[left] <= pivot) {
left++; left++;
} }
// 交换这两个元素 arr[right] = arr[left];
if (left < right) {
swap(arr, left, right);
} }
// 最后把枢轴填回中心位置
arr[left] = pivot;
return left;
} }
// 将枢轴放到最终位置
swap(arr, low, left);
return left; // 返回枢轴的位置
} }
public static void main(String[] args) { ```
int[] arr = {49, 38, 65, 97, 76, 13, 27, 49};
quickSort(arr, 0, arr.length - 1);
System.out.println("\n排序后:");
for (int num : arr) { ### 快速选择
System.out.print(num + " ");
时间复杂度: O(n)
```java
public class QuickSelect {
/**
* 在 nums[low..high] 区间内,寻找排序后下标为 k 的元素(第 k 小)
*/
public int quickselect(int[] nums, int low, int high, int k) {
// 区间内只有一个元素,直接返回
if (low == high) {
return nums[low];
}
// 选取区间第一个元素作为枢轴
int pivot = nums[low];
int left = low, right = high;
// “挖坑填坑”分区:左边填小于 pivot 的值,右边填大于 pivot 的值
while (left < right) {
// 从右向左找第一个 < pivot
while (left < right && nums[right] >= pivot) {
right--;
}
nums[left] = nums[right]; // 填到左“坑”
// 从左向右找第一个 > pivot 的
while (left < right && nums[left] <= pivot) {
left++;
}
nums[right] = nums[left]; // 填到右“坑”
}
// 把 pivot 放回最终位置
nums[left] = pivot;
// 根据 pivot 位置与 k 比较,决定去哪一边继续
if (left == k) {
return nums[left];
} else if (k < left) {
return quickselect(nums, low, left - 1, k);
} else {
return quickselect(nums, left + 1, high, k);
} }
} }
/**
* 返回数组中第 k 大的元素
* @param nums 输入数组
* @param k 1-based第 k 大
*/
public int findKthLargest(int[] nums, int k) {
int n = nums.length;
// 第 k 大对应第 (n - k) 小
return quickselect(nums, 0, n - 1, n - k);
}
} }
``` ```
@ -1379,8 +1432,8 @@ public class DescendingSortExample {
// 使用Comparator进行降序排序使用lambda表达式 // 使用Comparator进行降序排序使用lambda表达式
Arrays.sort(arr, (a, b) -> Integer.compare(b, a)); Arrays.sort(arr, (a, b) -> Integer.compare(b, a));
// 或者使用Collections.reverseOrder()也可以:
// 或者使用Collections.reverseOrder()也可以:
// 对下标 [1, 4) 的区间,也就是 {2,9,1},按降序排序 // 对下标 [1, 4) 的区间,也就是 {2,9,1},按降序排序
Arrays.sort(arr, 1, 4, Collections.reverseOrder()); Arrays.sort(arr, 1, 4, Collections.reverseOrder());

View File

@ -693,9 +693,20 @@ docker images
### **docker可能遇到的问题** ## Docker部署可能遇到的问题
- **linux中构建镜像问题** - **镜像拉取失败**
排除镜像站的问题如果拉取不到镜像很有可能是你使用的TAG不正确
直接去[maven Tags | Docker Hub](https://hub.docker.com/)上看看你输入的TAG是否存在比如
[Docker Hub](https://hub.docker.com/_/maven/tags)
如果你输入的都是个错误的TAG那肯定拉不下来。
- **构建镜像失败**
```text ```text
RuntimeError: cant start new thread。 RuntimeError: cant start new thread。
@ -706,8 +717,6 @@ RuntimeError: cant start new thread。
解决方法在dockerfile文件中增加一行关闭pip进度条展示 解决方法在dockerfile文件中增加一行关闭pip进度条展示
RUN pip config set global.progress_bar off RUN pip config set global.progress_bar off
```text ```text
=> [flask_app internal] load build definition from Dockerfile 0.0s => [flask_app internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 982B 0.0s => => transferring dockerfile: 982B 0.0s
@ -726,8 +735,6 @@ exit status 1
- **docker运行权限问题** - **docker运行权限问题**
```text ```text

View File

@ -1108,3 +1108,187 @@ axios.post("http://yapi.smart-xwork.cn/mock/169327/emp/deleteById","id=1").then(
``` ```
Vue中先定义emps空数组再axios将数据取到里面 this.emps=xxxx Vue中先定义emps空数组再axios将数据取到里面 this.emps=xxxx
## Nginx
**docker-compose.yml**
```yml
version: '3.8'
networks:
group-buy-network:
external: true
services:
# 前端(保持不变)
group-buy-market-front:
image: nginx:alpine
ports: ['86:80']
volumes:
- ./nginx/html:/usr/share/nginx/html
- ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf:ro
networks:
- group-buy-network
# 后端实例1
group-buying-sys-1:
image: smile/group-buying-sys:latest
ports: ['8091:8091']
networks:
- group-buy-network
# 后端实例2
group-buying-sys-2:
image: smile/group-buying-sys:latest # 使用相同镜像
ports: ['8092:8091'] # 宿主机端口不同,容器内部端口相同
networks:
- group-buy-network
```
**nginx.conf**
```nginx
# 全局配置块
user nginx; # 以nginx用户身份运行worker进程安全性考虑
worker_processes auto; # 自动根据CPU核心数设置工作进程数量优化性能
error_log /var/log/nginx/error.log warn; # 错误日志路径,只记录警告及以上级别的错误
pid /var/run/nginx.pid; # 存储Nginx主进程ID的文件位置用于进程管理
# 事件处理模块配置
events {
worker_connections 1024; # 每个worker进程能处理的最大并发连接数
# 总并发量 = worker_processes * worker_connections
}
# HTTP服务器配置
http {
# 基础文件类型支持
include /etc/nginx/mime.types; # 包含MIME类型定义文件扩展名与Content-Type映射
default_type application/octet-stream; # 默认MIME类型当无法识别文件类型时使用
# 性能优化参数
sendfile on; # 启用高效文件传输模式(零拷贝技术)
keepalive_timeout 65; # 客户端保持连接的超时时间减少TCP握手开销
# 上游服务器组定义(负载均衡)
upstream gbm_backend {
# 负载均衡配置(两个后端实例)
server group-buying-sys-1:8091 weight=3; # 后端实例1权重为3获得60%流量)
server group-buying-sys-2:8091 weight=2; # 后端实例2权重为2获得40%流量)
keepalive 32; # 保持到后端服务器的长连接数(提升性能)
}
# 支付服务上游(单实例)
upstream pay_backend {
server pay-mall:8092; # 支付服务地址Docker服务名
}
# 虚拟主机配置一个server代表一个网站
server {
# 监听配置
listen 80 default_server; # 监听IPv4的80端口作为默认服务器
listen [::]:80 default_server; # 监听IPv6的80端口
server_name groupbuy.bitday.top; # 绑定的域名(支持多个,用空格分隔)
# 静态文件服务配置
root /usr/share/nginx/html; # 前端静态文件根目录Docker需挂载此目录
index index.html; # 默认访问的索引文件
# 前端路由处理适配Vue/React等单页应用
location / {
try_files $uri $uri/ /index.html; # 尝试顺序匹配文件→匹配目录→回退到index.html
}
# 组购系统API代理配置
location /api/v1/gbm/ {
proxy_pass http://gbm_backend; # 请求转发到负载均衡组
proxy_set_header Host $host; # 传递原始域名
proxy_set_header X-Real-IP $remote_addr; # 传递客户端真实IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 代理链IP信息
}
# 支付/登录API代理正则匹配
location ~ ^/api/v1/(alipay|login)/ {
proxy_pass http://pay_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
```
**注意事项:**
1.由于是docker部署要区别宿主机端口与容器内端口比如 `server group-buying-sys-1:8091 weight=3;` 这里的8091就是 `group-buying-sys-1` 这个容器内端口。类似地nginx也是docker部署通过宿主机端口86映射到`group-buy-market-front`这个容器内的80端口。`nginx.conf`配置中的端口一定是和容器内挂钩的,而不是和宿主机!!!
2.如果不是docker部署比如在本地windows运行`nginx.exe` ,那么 就没有 `ports: ['8091:8091']` 这种映射关系你的java项目中写的端口是多少那么`nginx.conf`就配置多少
3.docker部署如果 `nginx``group-buying-sys` 位于同一网络中,可以`服务名:容器内端口` 等价于 `宿主机ip:端口`。比如 `group-buying-sys-2``ports: ['8092:8091']` 假设服务器ip地址为`124.71.159.xxx` ,那么
```nginx
upstream gbm_backend {
server group-buying-sys-2:8091 weight=2; #推荐 nginx->后端容器
}
```
```nginx
upstream gbm_backend {
server 124.71.159.xxx:8092 weight=2; #非常不推荐相关nginx->宿主机->后端容器
}
```
两种效果一样,但推荐第一种!!!
4.Nginx 反向代理的核心配置
```nginx
location /api/v1/gbm/ {
proxy_pass http://gbm_backend; # 请求转发到负载均衡组
proxy_set_header Host $host; # 传递原始域名
proxy_set_header X-Real-IP $remote_addr; # 传递客户端真实IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 代理链IP信息
}
```
将所有匹配 `/api/v1/gbm/` 路径的请求转发到 `gbm_backend` 这个后端容器(可负载均衡为多台)。
请求 `http://nginx-server/api/v1/gbm/order` → 转发到 `http://gbm_backend/api/v1/gbm/order`
**注意,**如果
```nginx
location /api/v1/gbm/ {
proxy_pass http://gbm_backend/; # 注意末尾的斜杠
}
```
请求 `http://nginx-server/api/v1/gbm/order` → 转发到 `http://gbm_backend/order``/api/v1/gbm/` 被移除)
5.如果配置了反向代理第4点内容那么前端调用后端接口时HTML、JS中无需写具体的后端服务器所在地址ip:端口),而且如果做了负载均衡,有多台,这里也不好指定哪一台。
**为什么?**
比如前端发送请求fetch(`/api/v1/gbm/index/query_group_buy_market_config`)
发现匹配 `/api/v1/gbm` nginx根据配置就发给http://gbm_backend负载均衡组组里有`group-buying-sys-1``group-buying-sys-2`,根据负载均衡规则转发到其中一台后端服务器。
6.静态文件挂载
```nginx
server {
...
# 静态文件服务配置
root /usr/share/nginx/html; # 前端静态文件根目录Docker需挂载此目录
index index.html;
...
}
```
root 要指定静态文件根目录如果是docker部署一定要写**容器内**的位置;`index.html` 这个文件是`/usr/share/nginx/html`目录下的。

View File

@ -291,6 +291,26 @@ EndNode.apply() → 组装结果并返回 TrialBalanceEntity
## 拼团结算
<img src="https://pic.bitday.top/i/2025/07/15/p876v1-0.png" alt="d56fcfb9708eb0d99600c751808bb843" style="zoom:80%;" />
## 对接商城和拼团系统
![80e1ef00f3e6eca3f1e4f66ad1823aac](https://pic.bitday.top/i/2025/07/15/rdkird-0.jpg)
上面左侧小型支付商城用户下单过程。增加一个营销锁单从营销锁单中获取拼团营销拿到的优惠价格。之后小型商城继续往下走创建支付订单。右侧拼团交易平台提供营销锁单流程锁单目标、优惠试算规则过滤最终落库和返回结果订单ID、原始价格、折扣金额、支付金额、订单状态
下面小型支付商城在用户完成支付后,调用拼团**组队结算**更新当前xx拼团完成人数当拼团完成后**接收**回调消息执行后续交易结算发货(暂时模拟的)。
**注意**两个回调函数不要搞混1alipay用户支付成功后的回调告诉pay-mall当前用户支付完毕可以调用拼团**组队结算**。2group_buy_market的某teamId拼团达到目标人数拼团成功的回调告诉pay-mall可以进行下一步动作比如给该teamId下的所有人发货。
## 收获 ## 收获
### 实体对象 ### 实体对象
@ -1139,7 +1159,9 @@ Retrofit 在运行时会生成这个接口的实现类,帮你完成:
### 微信扫码登录流程 ### 公众号扫码登录流程
场景:用微信的能力来替你的网站做“扫码登录”或“社交登录”,代替自己写一整套帐号/密码体系。后台只需要基于 `openid` 做一次性关联(比如把某个微信号和你系统的用户记录挂钩),后续再次扫码就当作同一用户;
![image-20250711192110034](https://pic.bitday.top/i/2025/07/11/vrgj6u-0.png) ![image-20250711192110034](https://pic.bitday.top/i/2025/07/11/vrgj6u-0.png)
@ -1160,7 +1182,7 @@ Retrofit 在运行时会生成这个接口的实现类,帮你完成:
微信返回 `{ "access_token":"ACCESS_TOKEN_VALUE", "expires_in":7200 }`,后端缓存这个值(有效期约 2 小时)。 微信返回 `{ "access_token":"ACCESS_TOKEN_VALUE", "expires_in":7200 }`,后端缓存这个值(有效期约 2 小时)。
- 后端利用 `access_token` 创建二维码 ticket返给前端。 - 后端利用 `access_token` 创建二维码 ticket返给前端。**每次调用微信会返回不同的ticket**
**2.前端展示二维码** **2.前端展示二维码**
@ -1168,13 +1190,113 @@ Retrofit 在运行时会生成这个接口的实现类,帮你完成:
**3.微信回调后端** **3.微信回调后端**
- 用户确认扫描后,微信服务器向你预先配置的**回调 URL**(如 `POST /api/v1/weixin/portal/receive`)推送包含 `ticket``openid` 的消息。 - 用户确认**扫描后**,微信服务器向你预先配置的**回调 URL**(如 `POST /api/v1/weixin/portal/receive`)推送包含 `ticket``openid` 的消息。
- 后端:将 `ticket → openid` **存入缓存**`openidToken.put(ticket, openid)`);调用 `sendLoginTemplate(openid)` 给用户推送“登录成功”模板消息(手机公众号上推送,非网页) - 后端:将 `ticket → openid` **存入缓存**`openidToken.put(ticket, openid)`);调用 `sendLoginTemplate(openid)` 给用户推送“登录成功”模板消息(手机公众号上推送,非网页)
**4.前端获知登录结果** **4.前端获知登录结果**
- **轮询方式**:生成二维码后,前端每隔几秒向后端发送 `ticket`来验证登录状态,后端**查缓存**来判断 `ticket` 对应用户是否成功登录。 - **轮询方式**:生成二维码后,前端每隔几秒向后端 `check_login` 接口发送 `ticket`来验证登录状态,后端**查缓存**来判断 `ticket` 对应用户是否成功登录。
- **推送方式**:前端通过 WebSocket/SSE 建立长连接,后端回调处理完成后直接往该连接推送登录成功及 JWT。 - **推送方式**:前端通过 WebSocket/SSE 建立长连接,后端回调处理完成后直接往该连接推送登录成功及 JWT。
### 浏览器指纹获取登录ticket
在扫码登录流程的基础上改进!!!
**目的:**把「这张二维码ticket」严格绑在发起请求的那台浏览器上防止别的设备或会话**拿到同一个 ticket** 就能登录。
**1.生成指纹**
前端在用户打开「扫码登录页」时,先用 JS浏览器 API比如 User-Agent、屏幕分辨率、插件列表、Canvas 指纹等)算出一个唯一的浏览器指纹 `fp`
**2.获取 ticket 时携带指纹**
前端发起请求:
```bash
GET /api/v1/login/weixin_qrcode_ticket_scene?sceneStr=<fp>
```
后端执行:
```java
String ticket = loginPort.createQrCodeTicket(sceneStr);
sceneTicketCache.put(sceneStr, ticket); // 把 fp→ticket 映射进缓存
```
**3.扫码后轮询校验**
前端轮询:传入 `ticket``sceneStr` 指纹
```bash
GET /api/v1/login/check_login_scene?ticket=<ticket>&sceneStr=<fp>
```
后端逻辑(简化):
```java
// 1) 验证拿到的 sceneStr(fp) 对应的 ticket 是否一致
String cachedTicket = sceneTicketCache.getIfPresent(sceneStr);
if (!ticket.equals(cachedTicket)) {
// fp 不匹配,拒绝
return NO_LOGIN;
}
// 2) 再看 ticket→openid 有没有被写入扫码并回调后saveLoginState 会写入)
String openid = ticketOpenidCache.getIfPresent(ticket);
if (openid != null) {
// 同一浏览器,且已扫码确认,返回 openid或 JWT
return SUCCESS(openid);
}
return NO_LOGIN;
```
**4.回调时保存登录状态**
当用户扫描二维码,微信会回调你预定的接口地址,拿到 `ticket``openid` 后,调用:
```java
ticketOpenidCache.put(ticket, openid); // 保存 ticket→openid
```
注意 `ticketOpenidCache ``sceneTicketCache` 一般是一个Cache Bean这里只是为了更清晰。
**安全性提升**
- **防止“票据劫持”**:别人就算截获了这个 ticket想拿去自己那台机器上轮询也不行因为指纹对不上。
- **防止多人共用**:多个人在不同设备上**同时扫同一个码**,只有最先发起获取 ticket 的那台浏览器能完成登录。
### 无痕登录
“无痕登录”(又称“免扫码登录”或“静默登录”)的核心思想,是在用户首次通过二维码/授权完成登录后,给这台设备发放一份**长期信任凭证**,以后再访问就能悄无声息地登录,不再需要人为地再扫码或输入密码。
#### 典型流程
**1.初次登录(扫码授权)**
即前面**"浏览器指纹获取登录ticket"**的流程
**2.后续“无痕”自动登录**
1前端再次打开页面重新生成指纹
2前端调用“免扫码”接口仅传递指纹
3后端校验 fingerprint → openid
```java
String openid = sceneLoginCache.getIfPresent(sceneStr);
if (openid != null) {
// 直接返回登录态Session / JWT
return SUCCESS(openid);
} else {
// 指纹过期或未绑定,返回未登录,前端再走扫码流程
return NO_LOGIN;
}
```
4**成功后**,前端拿到 openid/JWT直接进入应用无需用户任何操作。

View File

@ -809,6 +809,27 @@ Maven 默认会用 `artifactId-version`来命名,eg
部署时占用问题端口:
```bash
Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:13306 -> 0.0.0.0:0: listen tcp 0.0.0.0:13306: bind: An attempt was made to access a socket in a way forbidden by its access permissions.
```
先查看是否端口被占用:
```shell
netstat -aon | findstr 5672
```
如果没有被占用那么就是windows的bug在CMD使用管理员权限重启NAT网络服务即可
```shell
net stop winnat
net start winnat
```
## 前端部署 ## 前端部署
直接部署开发完毕的前端代码,准备: 直接部署开发完毕的前端代码,准备: