原創 ds小龍哥 DS小龍哥 嵌入式技術資訊
2024年09月19日 13:21 重慶 5人聽過
一、前言
在眾多物聯網通訊協議中,MQTT(Message Queuing Telemetry Transport)因其輕量、高效的特點而被廣泛應用於各種物聯網場景。它不僅能夠滿足裝置低功耗的需求,還支援裝置與雲端之間穩定可靠的訊息傳遞,是實現裝置上雲的理想選擇之一。本文介紹利用EMQX伺服器搭建自己私有的MQTT伺服器,構建一套完整的物聯網解決方案,涵蓋從裝置接入到資料儲存,再到資料轉發及上位機開發等多個關鍵環節。
EMQX,全稱為Erlang/Enterprise Middleware MQTT Broker,是一款基於Erlang/OTP平臺開發的開源物聯網訊息中介軟體。它專為大規模物聯網應用設計,能夠處理海量併發連線,並提供穩定的訊息釋出/訂閱服務。作為一款高效能的MQTT協議伺服器,EMQX不僅支援標準的MQTT v3.1、v3.1.1以及最新的v5.0版本協議,還提供了豐富的擴充套件功能來滿足不同場景下的需求。
EMQX的核心優勢在於其卓越的效能表現和高度可伸縮性。單個EMQX叢集可以輕鬆管理數百萬級別的裝置連線,同時保持低延遲的訊息傳遞能力。這使得EMQX成為構建大型物聯網系統時的理想選擇之一。此外,透過靈活配置規則引擎,使用者可以根據業務邏輯定製化處理接收到的資料流,實現複雜事件處理、資料轉換等功能。例如,當特定條件被觸發時,可以自動執行預設的動作或將資訊轉發給其他系統進行進一步分析。
安全性方面,EMQX支援多種認證機制如使用者名稱密碼驗證、客戶端證書驗證等,以確保只有授權使用者才能訪問敏感資源;同時也提供了TLS/SSL加密通訊能力,保障了資料傳輸過程中的安全性和完整性。對於需要嚴格遵守行業標準的企業來說,這些特性尤為重要。
在整合度方面,EMQX展現了極高的靈活性與相容性。無論是與其他資料庫系統的對接(如MySQL, PostgreSQL, MongoDB等),還是與各種雲服務提供商(如阿里雲、AWS)的無縫銜接,EMQX都能很好地適應並促進整個生態系統的健康發展。EMQX還配備了詳細的文件資料和技術支援服務,幫助開發者快速上手並解決遇到的問題。
圖片
本文章裡用到的全部工具軟體都可以在這裡下載(放在網盤裡了)。
https://pan.quark.cn/s/145a9b3f7f53
二、準備伺服器
EMQX-伺服器部署目前支援以下作業系統:
RedHat
CentOS
RockyLinux
AmazonLinux
Ubuntu
Debian
macOS
Linux
Windows(舊版本是支援的,新版不支援Windows系統,官網沒有下載連結了)
前期測試階段,華為雲、阿里雲、騰訊雲這些平臺都有免費試用的伺服器,可以領取一個月ECS伺服器試用測試。
也可以自己本地Windows電腦上搭建伺服器,這個我在另一篇文章裡有詳細介紹,還有對應的影片(Windows系統搭建)。
搭建自己的MQTT伺服器、實現裝置上雲(windows+EMQX)
我下面是領取了華為雲一個月的ECS伺服器搭建此專案的伺服器用於進行整體專案的測試。
2.1 登入官網
https://www.huaweicloud.com/
圖片
2.2 領取一個月的伺服器
【1】選擇伺服器
圖片
配置過程中,系統選擇ubuntu18.04。
【2】返回彈性伺服器的控制檯
伺服器領取配置好之後回到主介面。
圖片
【3】點選伺服器名字,可以進入到詳情頁面。
圖片
2.3 配置安全組
要確保MQTT伺服器常用的幾個埠已經開放出出來。
圖片
2.4 安裝FinalShell
Windows下安裝 FinalShell 終端,方便使用SSH協議遠端登入到雲伺服器。(當然,使用其他方式登入也是一樣的)
圖片
2.5 遠端登入到雲伺服器終端
【1】新建連線,選擇SSH連線。
圖片
【2】填入IP地址、使用者名稱、密碼
這裡的主機就是填伺服器的公網IP地址,密碼就是建立伺服器輸入的密碼,使用者名稱直接用root。
圖片
【3】點選連線伺服器
圖片
【4】第一次登入會彈出提示框,選擇接受並儲存
圖片
【5】接下來可以看到伺服器已經登入成功了。
圖片
三、Linux下安裝EMQX
本章節將介紹如何在 Ubuntu 系統中下載安裝並啟動 EMQX。
支援的 Ubuntu 版本:
Ubuntu 22.04
Ubuntu 20.04
Ubuntu 18.04
2.1 官網地址
連結:https://www.emqx.io/docs/zh/v5.2/deploy/install-ubuntu.html
2.2 透過Apt源安裝
EMQX 支援透過 Apt 源安裝,免除了使用者需要手動處理依賴關係和更新軟體包等的困擾,具有更加方便、安全和易用等優點。
在命令列終端,複製下面的命令過去,按下Enter鍵。
【1】透過以下命令配置 EMQX Apt 源:
curl -s https://assets.emqx.com/scripts/install-emqx-deb.sh | sudo bash
【2】執行以下命令安裝 EMQX:
sudo apt-get install emqx
【3】執行以下命令啟動 EMQX:
sudo systemctl start emqx
過程如下:
圖片
圖片
2.3 EMQX常用的命令
sudo systemctl emqx start 啟動
sudo systemctl emqx stop 停止
sudo systemctl emqx restart 重啟
四、配置EMQX-MQTT伺服器
4.1 登入EMQX內建管理控制檯
EMQX 提供了一個內建的管理控制檯,即 EMQX Dashboard。方便使用者透過 Web 頁面就能輕鬆管理和監控 EMQX 叢集,並配置和使用所需的各項功能。
在瀏覽器裡輸入: http://122.112.225.194:18083 就可以訪問EMQX的後臺管理頁面。可以管理以連線的客戶端或檢查執行狀態。
這裡面的IP地址,就是自己ECS雲伺服器的公網IP地址。
開啟瀏覽器後,輸入地址後開啟的效果:
圖片
預設使用者名稱和密碼:
使用者名稱:admin
密碼:public
第一次登入會提示你修改新密碼,如果不想設定,也可以選擇跳過(公網伺服器部署,還是要修改密碼安全些)。
下面修改新密碼:
圖片
登入成功的頁面顯示如下:
圖片
4.2 MQTT配置
這裡可以配置MQTT的一些引數,根據自己的需求進行配置。
圖片
4.3 測試MQTT通訊
新建一個客戶端,點選連線。
圖片
連線之後,然後點選訂閱,和釋出,如果下面訊息能正常的接收。說明MQTT伺服器通訊是已經正常,沒問題了。
並且在這個頁面也可以看到主題釋出和主題訂閱的格式。
圖片
4.4 MQTT客戶端登入伺服器測試
接下來就開啟我們自己的MQTT客戶端登入MQTT伺服器進行測試資料的通訊。
埠選擇: 1883
根據軟體引數填入引數,登入,進行主題的釋出和訂閱。
圖片
說明:目前還沒有配置客戶端認證,現在只要IP和埠輸入正確,MQTT三元組可以隨便輸入,都可以登入上伺服器的,伺服器沒有對三元組做校驗。
EMQ X 預設配置中啟用了匿名認證,任何客戶端都能接入 EMQX。沒有啟用認證外掛或認證外掛沒有顯式允許/拒絕(ignore)連線請求時,EMQX 將根據匿名認證啟用情況決定是否允許客戶端連線。
然後開啟EMQX的管理後臺,可以看到我們的裝置已經登入伺服器了,名字為test1。
圖片
在訂閱主題的頁面也可以看到我們客戶端裝置訂閱的主題。
圖片
4.5 客戶端認證配置
EMQX 預設配置中啟用了匿名認證,任何客戶端都能接入 EMQX。沒有啟用認證外掛或認證外掛沒有顯式允許/拒絕(ignore)連線請求時,EMQX 將根據匿名認證啟用情況決定是否允許客戶端連線。
在正式產品裡肯定是要啟用認證的,不然任何裝置都能接入。
下面就介紹如何配置 客戶端認證。
【1】開啟客戶端認證頁面
圖片
【2】選擇密碼認證
圖片
【3】選擇內建資料庫
圖片
【4】設定認證方式(都可以預設,不用改),直接點選建立。
圖片
【5】建立成功後,點選使用者管理
圖片
【6】新增使用者
圖片
圖片
【7】新增成功
圖片
【8】新增完畢之後,開啟MQTT客戶端可以進行測試。
登入的時候,MQTT使用者名稱和密碼必須輸入正確,按照上一步新增的資訊進行如實填寫,否則是無法登入伺服器的。
圖片
4.6 客戶端授權配置
客戶端授權頁面可以配置每個客戶端(裝置)的主題釋出,訂閱許可權。限制它是否可以釋出主題,訂閱主題。如果有需要就可以進行配置。
http://127.0.0.1:18083/#/authorization/detail/built_in_database?tab=users
【1】建立資料來源
圖片
【2】選擇內建資料庫
圖片
【3】完成建立
圖片
【4】點選許可權管理
圖片
【5】選擇客戶端ID,點選新增
圖片
【6】配置許可權
圖片
4.7 資料轉發(整合)
在整合選項裡,可以對裝置資料處理。比如:轉發到自己的HTTP伺服器,轉發到自己其他的MQTT伺服器,建立規則,某些事件觸發某些動作等等。
圖片
選擇資料橋接。
可以把資料傳送端自己的HTTP伺服器,或者傳送到其他的MQTT伺服器。
圖片
選擇HTTP服務 (如果自己有HTTP伺服器,可以將資料轉發給自己的HTTP伺服器)。
圖片
五、MQTT客戶端訊息互發測試
5.1 新增2個裝置
為了方便測試裝置間互相訂閱主題,資料收發,在客戶端認證頁面至少新增2個裝置。我這裡分別新增了test1和test2。
圖片
5.2 裝置間測試
裝置A訂閱裝置B的主題,裝置B訂閱裝置A的主題,實現資料互發。
圖片
裝置A的MQTT資訊:
MQTT伺服器地址:122.112.225.194
MQTT伺服器埠號:1883
MQTT客戶端ID:AAA
MQTT使用者名稱:test1
MQTT登入密碼:12345678
訂閱主題:BBB/#
釋出主題:AAA/1
釋出的訊息:{ "msg": "我是AAA裝置" }
裝置B的MQTT資訊:
MQTT伺服器地址:122.112.225.194
MQTT伺服器埠號:1883
MQTT客戶端ID:BBB
MQTT使用者名稱:test2
MQTT登入密碼:12345678
訂閱主題:AAA/#
釋出主題:BBB/1
釋出的訊息:{ "msg": "我是BBB裝置" }
五、微控制器裝置上雲
只要是MQTT客戶端能正常上雲通訊了,那麼微控制器也是一樣的。
上位機也可以採用MQTT協議接入伺服器,訂閱裝置的主題,就可以實時接收裝置的訊息(當然,也可以採用HTTP協議接入)。
六、資料橋接
EMQX支援將裝置上傳的資料轉發到其他地方,比如,自己的HTTP伺服器。方便自己伺服器進行其他的處理。
透過資料橋接,使用者可以實時地將訊息從 EMQX 傳送到外部資料系統,或者從外部資料系統拉取資料併傳送到 EMQX 的某個主題。而 EMQX Dashboard 提供了視覺化建立資料橋接的能力,只需在頁面中配置相關資源即可。
本章節就介紹如何搭建自己的HTTP伺服器。配置EMQX轉發資料到自己的HTTP伺服器,儲存處理資料。
6.1 搭建HTTP伺服器
我這裡直接使用python寫程式碼搭建一個HTTP伺服器。ECS伺服器上預設沒有安裝python3,需要先安裝一下。
【1】安裝python3
root@emqx:~/emqx# apt install python3
Reading package lists... Done
Building dependency tree
Reading state information... Done
python3 is already the newest version (3.6.7-1~18.04).
0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.
【2】編寫程式碼
from flask import Flask, json, request
app = Flask(__name__)
@app.route('/', methods=['POST'])
def print_messages():
reply= {"result": "ok", "message": "success"}
print("got post request: ", request.get_data())
return json.dumps(reply), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
將以上程式碼儲存到一個名為 server.py 的檔案中。
這段程式碼建立了一個使用 Flask 框架的 Web 伺服器,可以接收根路徑的 POST 請求。當接收到 POST 請求時,伺服器會將請求的資料列印到終端,並返回一個 JSON 格式的響應給客戶端。伺服器將在本地執行,並監聽預設的 8000 埠。
【3】執行程式
# 安裝 flask 依賴
pip install flask
pip3 install flask
# 啟動服務
python3 server.py
在命令列中執行 python3 server.py,就可以啟動一個簡單的HTTP伺服器,可以接收並處理POST請求。當有POST請求發生時,伺服器將返回收到的POST資料。可以根據自己的需要,進一步擴充套件處理POST請求的邏輯。
執行示例: (程式碼可以在本地寫好上傳到伺服器,也可以直接 vim server.py 開啟編輯器直接編寫 )
圖片
可以透過傳送 POST 請求到 http://your-server-ip:8000/ 的方式來測試這個伺服器。
比如:
http://122.112.225.194:8000/
6.2 資料轉發配置
【1】在整合選項裡,可以對裝置資料處理,將資料轉發到自己的HTTP伺服器。
圖片
【2】選擇Webhook。
Webhook,使用 Webhook 來轉發資料到 HTTP 服務;
使用 Webhook 其實就是將 EMQX 接收並處理後的資料傳送到一個 HTTP 服務上,再根據預設好的 HTTP 服務來處理和整合業務資料。
同樣使用者需要有一個預先搭建好的 HTTP 服務,需要在配置資訊頁面填寫 HTTP 請求的服務地址,選擇一個請求方法 POST、GET、PUT 或 DELETE,配置請求頭,將需要傳送的資料使用模板語法填寫到請求體(body)中即可。
圖片
【3】選配置Webhook
觸發器選擇所有訊息和事件,URL裡填自己的伺服器地址。
圖片
【4】點選測試。測試伺服器是否OK。
圖片
【6】沒問題就直接點選儲存
圖片
【7】建立成功
圖片
【8】儲存之後。會自動建立規則和資料橋接。非常方便。
圖片
圖片
圖片
6.3 測試轉發效果
【1】開啟MQTT客戶端,傳送資料測試。
圖片
【2】看python伺服器的終端,可以看到收到了EMQX伺服器轉發過來的資料。
如果自己接下來想要進行其他的操作,伺服器寫程式碼進行對應的處理即可。
圖片
【3】 點選這個服務可以看到已經觸發轉發的詳情。
圖片
圖片
七、API介面說明
一般開發一套完整的物聯網產品。一般會分為裝置端,伺服器,上位機部分。
裝置端:就是硬體端。採集本地感測器的資料上傳到伺服器,或者接收伺服器下發的指令完成某些控制。
比如:STM32 + ESP8266 + 各種感測器 就是一個硬體裝置端。可以透過ESP8266聯網上傳資料。
伺服器:也就是MQTT伺服器端,比如: 自己採用EMQX搭建的MQTT伺服器,或者採用阿里雲、華為雲、OneNet這些平臺的IOT伺服器。
上位機:上位機就是給使用者使用的,用於遠端控制裝置,檢視裝置。比如:微信小程式、手機APP、電腦上位機、web網頁端等等。
那麼這個章節,就介紹利用EMQX提供的API介面與MQTT客戶端裝置進行通訊,完成資料上傳,命令下發等功能,可以利用此介面完成上位機的開發。
7.1 檢視全部的API介面
幫助文件地址: https://www.emqx.io/docs/zh/v5.0/admin/api.html#%E8%AE%A4%E8%AF%81
EMQX 提供了管理監控 REST API,這些 API 遵循 OpenAPI (Swagger) 3.0 規範。 EMQX 在 REST API 上做了版本控制,EMQX 5.0.0 以後的所有 API 呼叫均以 /api/v5 開頭。
EMQX 服務啟動後,可以訪問 http://localhost:18083/api-docs/index.html (opens new window)來檢視 API 的文件。還可以直接在 Swagger UI 上嘗試執行一些 API。
比如: 我的EMQX伺服器是在華為雲ECS伺服器上搭建,公網IP是: 122.112.225.194
那我訪問API文件的地址就是下面這樣的格式: 在瀏覽器裡開啟即可。
http://122.112.225.194:18083/api-docs/index.html
訪問效果如下:
圖片
7.2 建立API密匙
【1】登入EMQX的後臺管理頁面: http://122.112.225.194:18083/
【2】找到選單裡的 系統設定選項-->API密匙。
圖片
【3】建立密匙。
圖片
【4】填寫密匙名稱
圖片
【5】建立成功
圖片
【6】得到API Key 和 Secret Key
API Key : f072a6e9758b8cdf
Secret Key : LzwPB71Yf7PTED39C7RGboz9C9ANhv83ULUynTANgog4hG
7.3 測試API: 獲取節點資訊
上一步已經建立好API的訪問密匙,這裡就以 獲取節點資訊為例,呼叫獲取節點資訊的API介面,獲取節點 資訊。
介面在API文件裡的介紹:
圖片
根據前面的API訪問路徑規則說明;那麼,獲取節點資訊的API完整訪問路徑為:
http://122.112.225.194:18083/api/v5/nodes
接下來就用python寫一份程式碼,測試一下介面是否可以正常訪問。python程式碼直接放伺服器執行(主要是我本地沒有安裝python環境,雲伺服器的環境是已經安裝OK的,測試方便)。
【1】在雲伺服器上建立一個python檔案,方便測試程式碼
圖片
【2】建立之後FinaShell自動上傳到伺服器
圖片
【3】編輯程式碼
在這裡雙擊要編輯的檔案,就可以開啟檔案進行編輯。預設採用內建的編輯器,也可以選擇自己電腦上的外接編輯器。
圖片
【4】程式碼編輯完成,按下鍵盤快捷鍵Ctrl + S 儲存檔案內容,儲存之後檔案內容會自動同步到伺服器。
圖片
儲存後提示,自動上傳。
圖片
寫入的程式碼如下:
import urllib.request
import json
import base64
username = 'f072a6e9758b8cdf'
password = 'LzwPB71Yf7PTED39C7RGboz9C9ANhv83ULUynTANgog4hG'
url = 'http://122.112.225.194:18083/api/v5/nodes'
req = urllib.request.Request(url)
req.add_header('Content-Type', 'application/json')
auth_header = "Basic " + base64.b64encode((username + ":" + password).encode()).decode()
req.add_header('Authorization', auth_header)
with urllib.request.urlopen(req) as response:
data = json.loads(response.read().decode())
print(data)
【5】執行程式碼,返回結果
透過返回資訊來看,節點資訊獲取是沒有問題的。
root@emqx:~/emqx# python3 http_api_test.sh
[{'connections': 0, 'edition': 'Opensource', 'live_connections': 0, 'load1': 0.0, 'load15': 0.0, 'load5': 0.0, 'log_path': '/var/log/emqx', 'max_fds': 1048576, 'memory_total': '3.66G', 'memory_used': '612.59M', 'node': 'emqx@127.0.0.1', 'node_status': 'running', 'otp_release': '25.3.2-2/13.2.2', 'process_available': 2097152, 'process_used': 543, 'role': 'core', 'sys_path': '/usr/lib/emqx', 'uptime': 84000040, 'version': '5.3.1-alpha.1'}]
root@emqx:~/emqx# python3 http_api_test.sh
[{'connections': 0, 'edition': 'Opensource', 'live_connections': 0, 'load1': 0.0, 'load15': 0.0, 'load5': 0.0, 'log_path': '/var/log/emqx', 'max_fds': 1048576, 'memory_total': '3.66G', 'memory_used': '613.23M', 'node': 'emqx@127.0.0.1', 'node_status': 'running', 'otp_release': '25.3.2-2/13.2.2', 'process_available': 2097152, 'process_used': 543, 'role': 'core', 'sys_path': '/usr/lib/emqx', 'uptime': 84008046, 'version': '5.3.1-alpha.1'}]
圖片
7.4 線上除錯(獲取主題列表)
在編寫程式碼之前,可以先測試下API介面的效果,可以直接在Swagger UI介面直接除錯API。
地址: http://122.112.225.194:18083/api-docs/index.html#/
例如:以獲取以訂閱主題列表的API介面為例。
【1】在Swagger UI介面上找到對應的API介面。
圖片
【2】點選API說明,展開詳情
圖片
【3】點選右邊的試試看按鈕。
圖片
【4】點選執行
圖片
【5】然後會彈出提示框,讓你填入使用者名稱和密碼。
這個使用者名稱和密碼就是前面建立API密匙生成的API Key(使用者名稱) 和 Secret Key(密碼) 。
API Key : f072a6e9758b8cdf
Secret Key : LzwPB71Yf7PTED39C7RGboz9C9ANhv83ULUynTANgog4hG
圖片
【6】根據提示輸入使用者名稱和密碼,再點選登入。
圖片
【7】再次點選執行,就可以看到介面返回的資料了。
並且在頁面上也寫出了,請求的資訊。使用curl命令列給出詳細的請求過程,參考這個就可以自己寫程式碼了。
圖片
【8】API呼叫,curl命令列執行程式碼如下:
curl -X 'GET' \
'http://122.112.225.194:18083/api/v5/topics?node=emqx%40127.0.0.1&page=1&limit=50' \
-H 'accept: application/json'
【9】python程式碼實現
import requests
url = 'http://122.112.225.194:18083/api/v5/topics?node=emqx%40127.0.0.1&page=1&limit=50'
headers = {'accept': 'application/json'}
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
# 在這裡處理返回的資料
print(data)
else:
print("請求失敗,狀態碼:", response.status_code)
7.5 線上除錯(釋出主題)
API裡也支援釋出主題,利用HTTP協議釋出主題訊息,如果裝置端訂閱了該主題,就可以收到API介面釋出的訊息。
【1】先找到釋出主題的API介面
圖片
【2】點選API名字,展開詳情
圖片
【3】點選右邊的Try it out按鈕。
圖片
【4】引數填寫說明
因為這個介面是傳送主題的,需要填引數,填自己需要釋出什麼主題,什麼訊息。
出來的框框裡就是釋出資訊,根據自己需要修改。
圖片
topic就是釋出的主題。 payload 就是釋出的訊息內容。只要MQTT客戶端訂閱了這個主題,就可以收到釋出的訊息。
這個主題的名字可以隨便改的。我這裡就用預設的名字和內容測試。
圖片
{
"payload_encoding": "plain",
"topic": "api/example/topic",
"qos": 0,
"payload": "hello emqx api",
"properties": {
"payload_format_indicator": 0,
"message_expiry_interval": 0,
"response_topic": "some_other_topic",
"correlation_data": "string",
"user_properties": {
"foo": "bar"
},
"content_type": "text/plain"
},
"retain": false
}
【5】MQTT客戶端登入。
開啟MQTT客戶端,登入伺服器,訂閱api/example/topic主題。
圖片
【6】在API除錯頁面,點選執行
圖片
【7】執行之後,在MQTT客戶端的就可以收到API下發的訊息了。
圖片
【8】API介面呼叫,curl命令列執行的程式碼如下:
curl -X 'POST' \
'http://122.112.225.194:18083/api/v5/publish' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"payload_encoding": "plain",
"topic": "api/example/topic",
"qos": 0,
"payload": "hello emqx api",
"properties": {
"payload_format_indicator": 0,
"message_expiry_interval": 0,
"response_topic": "some_other_topic",
"correlation_data": "string",
"user_properties": {
"foo": "bar"
},
"content_type": "text/plain"
},
"retain": false
}'
【9】Python程式碼實現
import requests
import json
url = 'http://122.112.225.194:18083/api/v5/publish'
headers = {
'accept': 'application/json',
'Content-Type': 'application/json'
}
data = {
"payload_encoding": "plain",
"topic": "api/example/topic",
"qos": 0,
"payload": "hello emqx api",
"properties": {
"payload_format_indicator": 0,
"message_expiry_interval": 0,
"response_topic": "some_other_topic",
"correlation_data": "string",
"user_properties": {
"foo": "bar"
},
"content_type": "text/plain"
},
"retain": False
}
response = requests.post(url, headers=headers, data=json.dumps(data))
if response.status_code == 200:
print("訊息釋出成功")
else:
print("訊息釋出失敗,狀態碼:", response.status_code)
ds小龍哥
閱讀 1.6萬
修改於2024年09月19日