## 好文
* [捫心自問,你真的熟練掌握MQ了嗎](https://mp.weixin.qq.com/s/gqFVeZIE6nfYQd57pQkfaA)
## 使用Docker構建RabbitMQ
查找鏡像
```
docker search rabbitmq
```
拉取鏡像
```
docker pull rabbitmq:3.7.16-management
```
> 默認情況下,會拉取rabbitmq的latest版本。
> 這里拉取 Web瀏覽器管理頁面的tag `3.7.16-management`【管理插件】
啟動鏡像
```
docker run -p 15672:15672 -p 5672:5672 -d --hostname dnmp-rabbitmq --name dnmp-rabbitmq --network dnmp_backend -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin rabbitmq:3.7.16-management
```
> 參數解釋
* `15672` :表示 RabbitMQ 控制臺端口號,可以在瀏覽器中通過控制臺來執行 RabbitMQ 的相關操作。
* `5672 `: 表示 RabbitMQ 所監聽的 TCP 端口號,應用程序可通過該端口與 RabbitMQ 建立 TCP 連接,完成后續的異步消息通信
* `RABBITMQ_DEFAULT_USER`:用于設置登陸控制臺的用戶名,這里我設置 `admin `
* `RABBITMQ_DEFAULT_PASS`:用于設置登陸控制臺的密碼,這里我設置 `admin `容器啟動成功后,可以在瀏覽器輸入地址:`http://ip:15672/ `訪問控制臺
## PHP 客戶端庫
> 以下以ThinkPHP5.1 框架為測試環境
#### 安裝擴展庫 [php-amqplib](https://github.com/php-amqplib/php-amqplib)
composer installed
```
docker run --rm --interactive --tty -v e:/dnmp/www/iot.tinywan.com:/app composer require php-amqplib/php-amqplib v2.9.0 --ignore-platform-reqs
```
## 第一章 開始代碼
##### 消息發布者(發送者)
> `mq_send.php` 腳本
```
#!/usr/bin/env php
<?php
namespace think;
define('APP_PATH', __DIR__ . '/application/');
require __DIR__ . '/thinkphp/base.php';
Container::get('app',[APP_PATH])->bind('http/RabbitMq/send')->run()->send();
```
>[warning] 業務代碼
```
public function send()
{
$connection = new AMQPStreamConnection('dnmp-rabbitmq', 5672, 'admin', 'admin');
$channel = $connection->channel();
$channel->queue_declare('hello', false, false, false, false);
$msg = new AMQPMessage('Hello World!');
$channel->basic_publish($msg, '', 'hello');
echo " [x] Sent 'Hello World!'\n";
$channel->close();
$connection->close();
}
```
>[danger] 注意:
> 1、 `user `和 `password `就是docker啟動時候的 `RABBITMQ_DEFAULT_USER=admin`和 `RABBITMQ_DEFAULT_PASS=admin`。前面我們設置的 `admin`和`admin`
> 2、連接主機`host`是`dnmp-rabbitmq`,由于是在docker容器之內
>[info] 在終端中,運行消費者(接收者)
```
> docker exec -it dnmp-php72 sh -c "php /var/www/iot.tinywan.com/mq_receive.php"
[*] Waiting for messages. To exit press CTRL+C
[x] Received Hello World!
[x] Received Hello World!
[x] Received Hello World!
[x] Received Hello World!
```
##### 消息接收者(發送者)
`mq_receive.php`腳本
```
#!/usr/bin/env php
<?php
namespace think;
define('APP_PATH', __DIR__ . '/application/');
require __DIR__ . '/thinkphp/base.php';
Container::get('app',[APP_PATH])->bind('http/RabbitMq/receive')->run()->send();
```
>[warning] 業務代碼
```
public function receive()
{
$connection = new AMQPStreamConnection('dnmp-rabbitmq', 5672, 'admin', 'admin');
$channel = $connection->channel();
$channel->queue_declare('hello', false, false, false, false);
echo " [*] Waiting for messages. To exit press CTRL+C\n";
$callback = function ($msg) {
echo ' [x] Received ', $msg->body, "\n";
};
$channel->basic_consume('hello', '', false, true, false, false, $callback);
while (count($channel->callbacks)) {
$channel->wait();
}
}
```
>[info] 在終端中,運行發布者(發件人)
```
> docker exec -it dnmp-php72 sh -c "php /var/www/iot.tinywan.com/mq_send.php"
[x] Sent 'Hello World!'
```
## 第二章 隊列
您可能希望看到RabbitMQ有哪些隊列以及它們中有多少消息。您可以使用rabbitmqctl工具(作為特權用戶)執行此操作:
```
docker exec -it dnmp-rabbitmq sh -c "rabbitmqctl list_queues"
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
name messages
hello 0
```
### 循環調度
使用任務隊列的一個優點是能夠輕松地并行工作。如果我們正在積壓工作積壓,我們可以添加更多工人,這樣就可以輕松擴展。
首先,讓我們嘗試同時運行兩個`mq_worker.php`腳本。他們都會從隊列中獲取消息,但究竟如何呢?讓我們來看看。
你需要打開三個控制臺。兩個將運行`mq_worker.php`腳本。這些游戲機將成為我們的兩個消費者 - `C1`和`C2`。
> 第一個終端
```
PS E:\dnmp> echo C1
C1
PS E:\dnmp> docker exec -it dnmp-php72 sh -c "php /var/www/iot.tinywan.com/mq_work.php"
[*] Waiting for messages. To exit press CTRL+C
[x] Received First message
[x] Done
[x] Received Third message
[x] Done
[x] Received Fifth message
[x] Done
```
> 第二個終端
```
PS E:\dnmp> echo C2
C2
PS E:\dnmp> docker exec -it dnmp-php72 sh -c "php /var/www/iot.tinywan.com/mq_work.php"
[*] Waiting for messages. To exit press CTRL+C
[x] Received First message
[x] Done
[x] Received Third message
[x] Done
[x] Received Fifth message
[x] Done
```
>[warning] 第三個終端
我們將發布新任務。啟動消費者后,您可以發布一些消息:
```
docker exec -it dnmp-php72 sh -c "php /var/www/iot.tinywan.com/mq_new_task.php First message. "
docker exec -it dnmp-php72 sh -c "php /var/www/iot.tinywan.com/mq_new_task.php Second message. "
docker exec -it dnmp-php72 sh -c "php /var/www/iot.tinywan.com/mq_new_task.php Third message. "
docker exec -it dnmp-php72 sh -c "php /var/www/iot.tinywan.com/mq_new_task.php Fourth message. "
docker exec -it dnmp-php72 sh -c "php /var/www/iot.tinywan.com/mq_new_task.php Fifth message. "
```
#### 查看其他兩個worker是如何接受消息的
```
> docker exec -it dnmp-php72 sh -c "php /var/www/iot.tinywan.com/mq_work.php"
[*] Waiting for messages. To exit press CTRL+C
[x] Received First message
[x] Done
[x] Received Third message
[x] Done
[x] Received Fifth message
[x] Done
```
```
> docker exec -it dnmp-php72 sh -c "php /var/www/iot.tinywan.com/mq_work.php"
[*] Waiting for messages. To exit press CTRL+C
[x] Received Second message
[x] Done
[x] Received Fourth message
[x] Done
```
> 默認情況下,RabbitMQ將按順序將每條消息發送給下一個消費者。平均而言,每個消費者將獲得相同數量的消息。這種分發消息的方式稱為循環法。與三個或更多工人一起嘗試。
### 消息確認
執行任務可能需要幾秒鐘。你可能想知道如果其中一個消費者開始一項長期任務并且只是部分完成而死亡會發生什么。使用我們當前的代碼,一旦RabbitMQ向消費者發送消息,它立即將其標記為刪除。在這種情況下,如果你殺死一個工人,我們將丟失它剛剛處理的消息。我們還將丟失分發給這個特定工作者但尚未處理的所有消息。
但我們不想失去任何任務。如果工人死亡,我們希望將任務交付給另一名工人。
為了確保消息永不丟失,RabbitMQ支持[消息*確認*](https://www.rabbitmq.com/confirms.html)。消費者發回ack(nowledgement)告訴RabbitMQ已收到,處理了特定消息,RabbitMQ可以自由刪除它。
如果消費者死亡(其通道關閉,連接關閉或TCP連接丟失)而不發送確認,RabbitMQ將理解消息未完全處理并將重新排隊。如果同時有其他在線消費者,則會迅速將其重新發送給其他消費者。這樣你就可以確保沒有消息丟失,即使工人偶爾會死亡。
沒有任何消息超時;當消費者死亡時,RabbitMQ將重新發送消息。即使處理消息需要非常長的時間,也沒關系。
默認情況下,消息確認已關閉。現在是時候通過設置第四個參數來打開它們`basic_consume`到假(`true`表示*`沒有ACK*`),并從工作人員發送適當的確認,一旦我們有一個任務來完成。
#### `即將被消費`和`未被消費`的消息查看
```
> docker exec -it dnmp-rabbitmq sh -c "rabbitmqctl list_queues name messages_ready messages_unacknowledged"
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
name messages_ready messages_unacknowledged
task_queue 4 0
hello 0 0
```
> `messages_ready `:即將被消費
> `messages_unacknowledged
`:消費但是未被確認
> 這時候如果開啟一個work,則會全部被消費掉
```
> docker exec -it dnmp-php72 sh -c "php /var/www/iot.tinywan.com/mq_work.php"
[*] Waiting for messages. To exit press CTRL+C
[x] Received Fifth message
[x] Done
[x] Received Fifth message
[x] Done
[x] Received Fifth message
[x] Done
[x] Received Fifth message
[x] Done
```
再次查看,準備的消息已經全部被消費完了
```
> docker exec -it dnmp-rabbitmq sh -c "rabbitmqctl list_queues name messages_ready messages_unacknowledged"
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
name messages_ready messages_unacknowledged
task_queue 0 0
hello 0 0
```
### 消息持久性
我們已經學會了如何確保即使消費者死亡,任務也不會丟失。但是如果RabbitMQ服務器停止,我們的任務仍然會丟失。
當RabbitMQ退出或崩潰時,它將忘記隊列和消息,除非你告訴它不要。確保消息不會丟失需要做兩件事:我們需要將隊列和消息都標記為持久。
首先,我們需要確保RabbitMQ永遠不會丟失我們的隊列。為此,我們需要聲明它是持久的。為此,我們將第三個參數傳遞給`queue_declare`為`true`:
```
$channel->queue_declare('hello', false, true, false, false);
```
這里如果實驗
1、關閉RabbitMQ服務器:`docker stop dnmp-rabbitmq`
2、重啟RabbitMQ服務器:`docker start dnmp-rabbitmq`
3、重新查看消息同時消費消息,消息是否已經持久化在數據文件中`rabbitmqctl list_queues name messages_ready messages_unacknowledged`
4、開啟一個worker 消費消息,看看消息是否還在
## 第三章 發布訂閱
#### 交易所
讓我們快速瀏覽前面教程中介紹的內容:
* 甲*生產者*是發送消息的用戶的應用程序。
* 甲*隊列*是存儲消息的緩沖器。
* 甲*消費者*是接收消息的用戶的應用程序。
RabbitMQ中消息傳遞模型的核心思想是生產者永遠不會將任何消息直接發送到隊列。實際上,生產者通常甚至不知道消息是否會被傳遞到任何隊列。
- 序言
- 專題一 PHP基礎教程
- 1、empty、isset、is_null的用法
- 2、線程安全與非線程
- 3、大文件上傳需要修改的配置
- 10、魔術方法
- 4、編譯安裝PHP7
- 5、編譯安裝PHP7.4
- 6、PECL 安裝 PHP 擴展庫
- 專題二 PHP高級教程
- 1、類和對象
- 2、繼承
- 3、魔術方法
- 4、抽象類
- 5、接口
- 6、反射機制實現自動依賴注入
- 7、服務容器與依賴注入的思想
- 8、并發解決方案之opcache
- 9、Composer自動加載原理
- 1、安裝與使用
- 10、抽象類和接口的區別
- 11、self和static的區別
- 12、PHP7 變量
- 13、PHP8.3 錯誤 Error 和 異常 Exception 樹列表
- 專題三 ThinkPHP6專題
- 1、DI容器
- 2、AUTH權限認證
- 3、Nginx URI重寫方式
- 4、并發鎖問題
- 5、自定義全局異常
- 6、CLI 模式跨模塊查詢數據庫
- 7、數據庫優化方案
- 附錄一 常見錯誤
- 附錄二 自定義分頁類
- 附錄三 cropper.js圖片上傳和裁剪
- 附錄四 數據庫和模型源碼解讀
- 1、Db類
- 2、查詢構造器
- 3、模型
- 附錄五 權限認證Auth類
- 附錄六 ThinkPHP5.1 源碼分析
- (一)類的自動加載
- (二)配置文件Config類
- (三)容器Container類
- (四)門面Facade
- (五)框架執行流程
- (六)路由解析
- 附錄七 官方擴展
- 1、think-queue 隊列
- 附錄八 易錯整理
- 附錄九 問題列表
- 附錄十 任務隊列異步通過視圖導出PDF
- 專題四 Docker教程
- 1、Docker安裝
- 2、如何在本地構建鏡像
- 3、鏡像、容器以及命令操作
- 4、容器進入的4種方式
- 5、Dockerfile常用指令詳解
- 6、發布自己的鏡像
- 7、數據卷管理
- 8、docker-compose概念
- 9、docker-compose入門
- 10、如何構建docker-compose
- 11、Docker網絡
- 12、搭建私有倉庫
- 13、Docker部署方式
- 14、推送到Github倉庫
- 附錄一 PHPStrom 調試XDebug
- 附錄二 安裝RabbitMQ
- 附錄 日常使用筆記
- 附錄四 Docker 調試XDebug
- 專題五 Redis教程
- 1、編譯安裝
- 2、配置文件詳解
- 3、Lua 腳本的應用和實踐
- 4、Redis實現分布式鎖(集群版)
- 5、Redis鍵空間通知
- 6、Redis5.0 搭建集群
- (1)、創建和使用Redis群集
- (2)、新增節點
- 7、限流器的實現
- 8、Redis5.0 新特性
- 8.1、注意要點
- 8.2 xreadgroup 命令
- 9、延遲任務隊列
- 10 Stream 消息隊列
- 11、基于 Redis 的 Stream 類型的完美消息隊列解決方案
- 12、Streams 實現延遲消息隊列
- 13、Stream流三種ACK機制
- 14、read error on connection排查
- 附錄一 常見問題
- 附錄二 Redis面試大全
- 附錄三 有序集合使用場景
- 附錄四 Lua腳本調試
- 附錄五 高性能、高可擴展關鍵技術
- 專題六 MySQL教程
- 1-1、二進制安裝
- 1-2、安裝包安裝(推薦)
- 2、索引、鎖、事務
- 3、字符集
- 4、導出導入數據
- 5、5.7版本兼容性
- 6、數據庫自動備份
- 7、如何重置MySQL 5.7 root密碼
- 8、MySQL自動完成和語法突出
- 9、普通索引和唯一索引的區別
- 10、深入了解行鎖、表鎖、索引
- 11、索引數據結構
- 12、MySQL規范
- 13、開發高頻面試題精選(重要)
- 14、鎖專題
- 1、可重復讀(REPEATABLE_READ)
- 2、事務隔離級別概述
- 15、MySQL外鍵約束
- 16、left join 查詢
- 17、 MySQL設計三范式和反范式
- 18、性能分析-Profiling
- 19、查詢好慢,除了索引,還能因為什么?(重要)
- 20、常用日期字段
- MYSQL 5.7 VARCHAR 類型詳解
- 專題七 Nginx教程
- 1、什么是Nginx?
- 2、編譯安裝
- 3、日志配置和模塊講解
- 4、靜態資源和緩存服務
- 5、正向和反向服務
- 6、Rewrite規則
- 7、HTTP負載均衡(七層)
- 8、TCP負載均衡(四層)
- 9、如何配置HTTPS服務
- 10、Nginx的負載均衡算法
- 11、如何配置http和https同時訪問
- 12、灰度發布
- 13、常見負載均衡算法
- 14、Openresty 專題
- 15、如何改進 NGINX 配置文件節省帶寬?
- 16、談談基于 OpenResty 的接口網關設計
- 附錄一 阿里云負載均衡配置
- 附錄二、基礎配置文件
- nginx.conf
- 附錄三、Nginx+lua+Memcache 實現灰度發布
- 附錄四 視頻監控RTSP轉HLS解決方案
- 附錄五 Openresty 編譯
- 附錄六 Vod模塊
- 1、本地模式
- 2、映射模式
- 專題八 Git版本管理
- 1、Git 基礎知識
- 2、團隊分支模型
- 3、儲藏與清理
- 4、如何同步Fork
- 5、多Git賬戶id_rsa私鑰
- 6、高效規范使用Git
- 7、遠程分支的創建
- 8、GitFlow工作流
- 9、Git撤銷&回滾操作(git reset 和 get revert)
- 10、合并時 --no-ff 的作用
- 11、 刪除本地、遠程、緩存分支
- 13、Git和Windows的大小寫不敏感產生的問題
- 附錄一 、一次記錄
- 附錄二、常用工作流程
- 附錄三、每次更新代碼都要輸入用戶名密碼
- 附錄四、OEM版本控制
- 附錄五 常用記錄
- 1、查看某一個文件修改的具體內容
- 2、強制推送到遠程分支
- 3、生產環境代碼回滾
- 附錄六 三年 Git 使用心得 & 常見問題整理
- 附錄七 Git 忽略文件,不提交文件 清空緩存
- 12/找回歷史刪除分支
- 專題九 WorkerMan服務
- 3、SocketIO消息推送
- 4、master和worker模型
- 5、GatewayWorker
- 6、使用systemd管理workerman
- 7、TCP長連接應用GatewayWorker心跳檢測
- 附錄一 運行問題
- 附錄二 問題與解決方法
- 專題十 MQ消息中間件
- 1、為什么要使用消息隊列
- 2、RabbitMQ
- 『1』AMQP核心概念
- 『2』交換機模式講解
- 『3』RabbitMQ高級特性
- (1)hello
- 3、NSQ
- 4、RabbitMQ延遲隊列
- 附件一 RabbitMQ 注意要點
- 5、RocketMQ PHP 生產端和消費端代碼優雅實現
- 專題十一 PHP函數整理
- 1、系統函數
- 2、自定義函數
- 3、回調函數
- 4、匿名函數
- 5、遞歸函數
- 6、常用函數庫
- 7、call_user_func函數
- 8、preg_replace_callback函數
- 專題十二 常用設計模式
- 1、創建型模式
- (1)單例模式
- (2)工廠模式
- (3)抽象工廠模式
- (4)建造者模式(Builder)
- (5)原型模式(Prototype)
- 2、結構型模式
- (1)適配器模式(Adapter)
- (2)橋接模式(Bridge)
- (3)合成模式(Composite)
- (4)裝飾器模式(Decorator)
- (5)代理模式(Proxy)
- (6)享元模式(Flyweight)
- 3、行為型模式
- 2、策略模式( Strategy)
- 4、六大原則
- 1、依賴注入
- 5、其他
- 6、Presenter模式
- 4、Service 模式
- 5、Repository模式
- 外觀設計模式示例
- 專題十三 實時通信
- 1、pusher 入門教程
- 2、pusher 演示與頻道實時通信
- 3、pusher 如何使用私有頻道
- 4、pusher 實時圖表展示
- 11、webman插件push入門教程
- 12、webman插件push如何使用私有頻道
- 13、webman插件push私有頻道客戶端推送
- 14、webman插件push的webhooks
- 15、webman插件push的實時動態圖表
- 專題十四 PHP異常處理
- 1、Exception 類
- 2、如何自定義異常?
- 3、處理PHP重錯誤
- 4、自定義錯誤處理器
- 專題十五 Shell腳本案例
- 1、crontab任務腳本無法執行問題
- 專題十六 Jenkins自動化部署
- 1、Jenkins安裝
- 2、Pipeline插件
- 3、BlueOcean
- 4、OPENSSH PRIVATE KEY轉換為RSA PRIVATE KEY
- 專題十七 常用工具整理
- 1、證書在線打印
- 2、密碼生成規則
- 3、vscode插件
- 專題十八 常用功能列表
- 1、frp 內網穿透工具
- (1)如何做成一個服務
- (2)代理Websocket服務
- (3)代理N個Web服務
- (4)設置為系統服務
- (5)Https 配置
- 附錄一 常見問題排查
- 2、如何美化文檔
- 3、如何提高訪問github的速度?
- 4、Vultr搭建SS教程
- 5、PPH編譯安裝
- 6、Supervisor進程管理工具
- 7、Umeditor 上傳文件阿里云和本地
- 8、scp 遠程上傳或下載 文件/文件夾
- 9、安裝和使用守護進程Supervisor
- 10、人臉識別
- 專題十九 流媒體直播實戰
- 1、什么是視頻直播?
- 2、如何使用推流軟件OBS?
- 3、基于Nginx 的RTMP模塊搭建系統
- 4、直播流程
- 附錄一 阿里云直播
- 5、典型業務場景
- 8、直播回調授權觀看
- 9、視頻直播源如何加密
- 10、如何實現視頻在線云剪輯
- 11、視頻點播以及加密技術實現
- 12、FFmpeg 入門教程
- 13、HLS 直播加密播放
- 14、nginx-vod-module 模塊
- 15、車輛維修直播系統
- 『16』HLS-m3u8專題
- 附件一 FFmpeg 命令
- 附件二 阿里云點播
- 專題二十 微信
- 附錄一 遇到的坑
- 專題二十一 支付專題
- 『1』支付寶支付
- 『2』微信支付
- 『3』支付寶直付通
- 「1」什么是直付通?
- 「2」二級商戶進件
- 「3」統一交易收款
- 「4」資金結算
- 「5」分賬
- 「6」支付接入流程
- 「?」問題列表
- 附錄一 return_url和notify_url的區別
- 附錄二 常見錯誤信息
- 附錄三 電腦端支付案例
- 附錄四 其他問題
- 1、購買了線上課程后不能退款 這樣的現象合法嗎?
- 專題二十二 Vue3筆記
- 1、開發環境搭建
- 專題二十三 開放API專題
- 1、錯誤碼
- 2、O??Auth2流的簡單說明
- 『3』HTTP API 身份驗證和授權
- 專題二十四 測試專題
- 1、并發測試
- 專題二十五 DevOps專題
- 『1』PHP代碼質量實戰
- 專題二十六 前后端分離
- 『1』前后端分離介紹
- 『2』控制權限管理
- 專題二十七 微服務專題
- 1、服務發現 Nacos
- 1.1 服務發現
- 專題二十八 Casbin權限專題
- 1、設置超級管理員的三種方法
- 2、多租戶權限和基本設置
- 3、casbin簡化策略數據
- 4、多個RBAC
- 5、身份驗證和基于角色的RBAC授權
- 6、Casbin在RESTful及中間件使用
- 7、Casbin 中 ABAC 的使用方法
- 8、Model語法和策略存儲
- 9、Casbin的Model和Policy
- 10、RBAC的RESTful完全匹配訪問模型
- 11、自定義函數使用
- 12、webman中使用
- 13、分布式服務中如何使用Watcher
- 14、Casbin 項目實戰ABAC模型策略
- 附錄一 源碼解讀
- 附錄二 常見問題
- 專題二十九 PHP 常見錯誤處理
- 專題三十 ELK日志系統
- 1、docker-elk
- 專題三十一 Swoole專題
- 1、ThinkPHP6中RPC服務
- 專題三十二 Webman框架
- 1、自定義進程執行異步任務
- 2、實現WebRTC信令服務器
- 3、實現一個RPC服務
- 4、對象和資源的持久化
- 5、ThinkORM持久化連接
- 6、ThinkORM悲觀鎖解決商品超賣問題的實現
- 7、monolog日志神器
- 附錄一 為什么?
- 附錄二 編碼規范
- 附錄一 游戲
- 1、初級程序員常犯的錯誤
- 附錄三 設計模式
- 1、單例模式
- 2、工廠方法模式
- 3、抽象工廠模式
- 4、裝飾器模式
- 附錄四 Docker安裝SqlServer
- 專題二十一 Layui
- 我的技術棧【重要】
- 附錄四 Linux 日常運維
- 1、sudo 權限
- 2、用戶和用戶組管理
- 3、grep 多條件查詢
- 其他系列
- 1、fnm:基于Rust開發的高效Node版本管理工
- 樣式
- Mall商城
- 1、系統架構
- 1.1 mall整合ThinkPHP+ThinkORM搭建基本骨架
- 1.2 mall整合Elasticsearch實現商品搜索
- 1.3 mall整合OSS實現文件上傳
- 2、業務篇