第 5-2 課:使⽤ Spring Boot Actuator 監控應⽤

微服務的特點決定了功能模塊的部署是分佈式的,⼤部分功能模塊都是運⾏在不同的機器上,彼此通過服務
調⽤進⾏交互,前後臺的業務流會經過很多個微服務的處理和傳遞,出現異常如何快速定位便成爲了⼀個問
題,在這種框架下微服務的監控顯得尤爲重要。
 
Spring Boot 是⼀個⾃帶監控的開源框架,組件 Spring Boot Actuator 負責監控應⽤的各項靜態和動態的變
量。項⽬結合 Spring Boot Actuator 的使⽤,便可輕鬆對 Spring Boot 應⽤監控治理,Spring Boot
Actuator 提供了很多⽣產級的特性,⽐如監控和度量 Spring Boot 應⽤程序,這些特性可以通過衆多 REST
接⼝、遠程 Shell JMX 獲得。

Actuator 監控

Spring Boot 使⽤習慣優於配置的理念,採⽤包掃描和⾃動化配置的機制來加載依賴 jar 中的 Spring Bean
不需要任何 XML 配置,就可以實現 Spring 的所有配置。雖然這樣做能讓代碼變得⾮常簡潔,但是整個應⽤
的實例創建和依賴關係等信息都被離散到了各個配置類的註解上,這使得我們分析整個應⽤中資源和實例的
各種關係變得⾮常的困難。
 
只需要在項⽬中添加 spring-boot-starter-actuator,就⾃動啓⽤了監控功能。
<dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>
</dependencies>
添加 spring-boot-starter-web 主要是爲了保持應⽤運⾏狀態。
Spring Boot Actuator Spring Boot 提供的對應⽤系統的檢查和監控的集成功能,可以查看應⽤配置的詳細
信息,例如⾃動化配置信息、創建的 Spring beans 以及⼀些環境屬性等。

Actuator REST 接⼝

Actuator 監控分成兩類:原⽣端點和⽤戶⾃定義端點。⾃定義端點主要是指擴展性,⽤戶可以根據⾃⼰的實
際應⽤,定義⼀些⽐較關⼼的指標,在運⾏期進⾏監控。
 
原⽣端點是在應⽤程序⾥提供衆多 Web 接⼝,通過它們瞭解應⽤程序運⾏時的內部狀況,原⽣端點⼜可以分
成三類:
  • 應⽤配置類,可以查看應⽤在運⾏期的靜態信息,例如⾃動配置信息、加載的 springbean 信息、yml 件配置信息、環境信息、請求映射信息;
  • 度量指標類,主要是運⾏期的動態信息,如堆棧、請求連、⼀些健康指標、metrics 信息等;
  • 操作控制類,主要是指 shutdown,⽤戶可以發送⼀個請求將應⽤的監控功能關閉。
Actuator 提供了 13 個接⼝,具體如下表所示。

HTTP

⽅方法

 

路路徑

 

描述

GET

/auditevents

顯示應⽤用暴暴露露的審計事件(如認證進⼊入、訂單失敗)

GET

/beans

描述應⽤用程序上下⽂文⾥裏里全部的 Bean 以及它們的關係

 

GET

 

/conditions

就是 1.0 的 /autoconfig,提供⼀一份⾃自動配置⽣生效的條件情況,記錄哪些

⾃自動配置條件通過了了,哪些沒通過

GET

/configprops

描述配置屬性(包含默認值)如何注⼊入 Bean

GET

/env

獲取全部環境屬性

GET

/env/{name}

根據名稱獲取特定的環境屬性值

GET

/flyway

提供⼀一份 Flyway 數據庫遷移信息

GET

/liquidbase

顯示 Liquibase 數據庫遷移的纖細信息

GET

/health

報告應⽤用程序的健康指標,這些值由 HealthIndicator 的實現類提供

GET

/heapdump

dump ⼀一份應⽤用的 JVM 堆信息

GET

/httptrace

顯示 HTTP ⾜足跡,最近 100 個 HTTP request/repsponse

GET

/info

獲取應⽤用程序的定製信息,這些信息由 info 打頭的屬性提供

GET

/logfile

返回 log file 中的內容(如果 logging.file 或者 logging.path 被設置)

GET

/loggers

顯示和修改配置的 loggers

GET

/metrics

報告各種應⽤用程序度量量信息,⽐比如內存⽤用量量和 HTTP 請求計數

GET

/metrics/{name}

報告指定名稱的應⽤用程序度量量值

GET

/scheduledtasks

展示應⽤用中的定時任務信息

GET

/sessions

如果我們使⽤用了了 Spring Session 展示應⽤用中的 HTTP Sessions 信息

POST

/shutdown

關閉應⽤用程序,要求 endpoints.shutdown.enabled 設置爲 true

 

GET

 

/mappings

描述全部的 URI 路路徑,以及它們和控制器器(包含 Actuator 端點)的映射關係

GET

/threaddump

獲取線程活動的快照

命令詳解

Spring Boot 2.x 中爲了安全期間,Actuator 只開放了兩個端點 /actuator/health /actuator/info,可以在
配置⽂件中設置打開。
 
可以打開所有的監控點
management.endpoints.web.exposure.include=*
也可以選擇打開部分:
 
management.endpoints.web.exposure.exclude=beans,trace
Actuator 默認所有的監控點路徑都在 /actuator/*,當然如果有需要這個路徑也⽀持定製。
management.endpoints.web.base-path=/manage
設置完重啓後,再次訪問地址就會變成 /manage/*
Actuator ⼏乎監控了應⽤涉及的⽅⽅⾯⾯,我們重點講述⼀些經常在項⽬中常⽤的命令。

health

health 主要⽤來檢查應⽤的運⾏狀態,這是我們使⽤最⾼頻的⼀個監控點,通常使⽤此接⼝提醒我們應⽤實
例的運⾏狀態,以及應⽤不健康的原因,如數據庫連接、磁盤空間不夠等。
 
默認情況下 health 的狀態是開放的,添加依賴後啓動項⽬,訪問:http://localhost:8080/actuator/health 即可
看到應⽤的狀態。
{
 "status" : "UP"
}
默認情況下,最終的 Spring Boot 應⽤的狀態是由 HealthAggregator 彙總⽽成的,彙總的算法是:
  • 設置狀態碼順序爲 setStatusOrder(Status.DOWN, Status.OUTOFSERVICE, Status.UP, Status.UNKNOWN);
  • 過濾掉不能識別的狀態碼
  • 如果⽆任何狀態碼,整個 Spring Boot 應⽤的狀態是 UNKNOWN
  • 將所有收集到的狀態碼按照 1 中的順序排序
  • 返回有序狀態碼序列中的第⼀個狀態碼,作爲整個 Spring Boot 應⽤的狀態
health 通過合併⼏個健康指數檢查應⽤的健康情況。Spring Boot Actuator 有⼏個預定義的健康指標⽐如
DataSourceHealthIndicatorDiskSpaceHealthIndicatorMongoHealthIndicatorRedisHealthIndicator
等,它使⽤這些健康指標作爲健康檢查的⼀部分。
舉個例⼦,如果你的應⽤使⽤ RedisRedisHealthindicator 將被當作檢查的⼀部分;如果使⽤ MongoDBGitChat
那麼 MongoHealthIndicator 將被當作檢查的⼀部分。
 
可以在配置⽂件中關閉特定的健康檢查指標,⽐如關閉 Redis 的健康檢查:
management.health.redise.enabled=false
默認所有的這些健康指標被當作健康檢查的⼀部分。

詳細的健康檢查信息

默認只是展示了簡單的 UP DOWN 狀態,爲了查詢更詳細的監控指標信息,可以在配置⽂件中添加以下
信息:
 
management.endpoint.health.show-details=always
重啓後再次訪問⽹址 http://localhost:8080/actuator/health,返回信息如下:

 "status": "UP",
 "diskSpace": {
 "status": "UP",
 "total": 209715195904,
 "free": 183253909504,
 "threshold": 10485760
 }
}
可以看到 HealthEndPoint 給我們提供默認的監控結果,包含磁盤空間描述總磁盤空間,剩餘的磁盤空間和最
⼩閾值。
 
其實看 Spring Boot-actuator 源碼,會發現 HealthEndPoint 提供的信息不僅限於此,在
org.springframework.boot.actuate.health 包下會發現 ElasticsearchHealthIndicator
RedisHealthIndicatorRabbitHealthIndicator 等。

info

info 是我們⾃⼰在配置⽂件中以 info 開頭的配置信息,⽐如在示例項⽬中的配置是:

info.app.name=spring-boot-actuator
info.app.version= 1.0.0
info.app.test= test

啓動示例項⽬,訪問 http://localhost:8080/actuator/info 返回部分信息如下:

{
 "app": {
 "name": "spring-boot-actuator",
 "version": "1.0.0",
 "test":"test"
 }
}

beans

根據示例就可以看出,展示了 bean 的別名、類型、是否單例、類的地址、依賴等信息。
啓動示例項⽬,訪問⽹址 http://localhost:8080/actuator/beans 返回部分信息如下:
 
[
 {
 "context": "application:8080:management",
 "parent": "application:8080",
 "beans": [
 {
 "bean": "embeddedServletContainerFactory",
 "aliases": [
 
 ],
 "scope": "singleton",
 "type": "org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedSe
rvletContainerFactory",
 "resource": "null",
 "dependencies": [
 
 ]
 },
 {
 "bean": "endpointWebMvcChildContextConfiguration",
 "aliases": [
 
 ],
 "scope": "singleton",
 "type": "org.springframework.boot.actuate.autoconfigure.EndpointWebMvcChil
dContextConfiguration$$EnhancerBySpringCGLIB$$a4a10f9d",
 "resource": "null",
 "dependencies": [
 
 ]
 }
 }
]

conditions

Spring Boot 的⾃動配置功能⾮常便利,但有時候也意味着出問題⽐較難找出具體的原因。使⽤ conditions
以在應⽤運⾏時查看代碼瞭解某個配置在什麼條件下⽣效,或者某個⾃動配置爲什麼沒有⽣效。
啓動示例項⽬,訪問⽹址 http://localhost:8080/actuator/conditions 返回部分信息如下:
{
 "positiveMatches": {
 "DevToolsDataSourceAutoConfiguration": {
 "notMatched": [
 {
 "condition": "DevToolsDataSourceAutoConfiguration.DevToolsData
SourceCondition", 
 "message": "DevTools DataSource Condition did not find a singl
e DataSource bean"
 }
 ], 
 "matched": [ ]
 }, 
 "RemoteDevToolsAutoConfiguration": {
 "notMatched": [
 {
 "condition": "OnPropertyCondition", 
 "message": "@ConditionalOnProperty (spring.devtools.remote.sec
ret) did not find property 'secret'"
 }
 ], 
 "matched": [
 {
 "condition": "OnClassCondition", 
 "message": "@ConditionalOnClass found required classes 'javax.
servlet.Filter', 'org.springframework.http.server.ServerHttpRequest'; @Conditional
OnMissingClass did not find unwanted class"
 }
 ]
 }
 }
}

confifigprops

查看配置⽂件中設置的屬性內容以及⼀些配置屬性的默認值。
啓動示例項⽬,訪問⽹址 http://localhost:8080/actuator/confifigprops 返回部分信息如下:
 
{
 ...
 "environmentEndpoint": {
 "prefix": "endpoints.env",
 "properties": {
 "id": "env",
 "sensitive": true,
 "enabled": true
 }
 },
 "spring.http.multipart-org.springframework.boot.autoconfigure.web.MultipartPrope
rties": {
 "prefix": "spring.http.multipart",
 "properties": {
 "maxRequestSize": "10MB",
 "fileSizeThreshold": "0",
 "location": null,
 "maxFileSize": "1MB",
 "enabled": true,
 "resolveLazily": false
 }
 },
 "infoEndpoint": {
 "prefix": "endpoints.info",
 "properties": {
 "id": "info",
 "sensitive": false,
 "enabled": true
 }
 }
 ...
}

env

展示了系統環境變量的配置信息,包括使⽤的環境變量、JVM 屬性、命令⾏參數、項⽬使⽤的 jar 包等信
息。和 confifigprops 不同的是,confifigprops 關注於配置信息,env 關注運⾏環境信息。
啓動示例項⽬,訪問⽹址 http://localhost:8080/actuator/env 返回部分信息如下:
{
 "profiles": [
 
 ],
 "server.ports": {
 "local.management.port": 8088,
 "local.server.port": 8080
 },
 "servletContextInitParams": {
 
 },
 "systemProperties": {
 "com.sun.management.jmxremote.authenticate": "false",
 "java.runtime.name": "Java(TM) SE Runtime Environment",
 "spring.output.ansi.enabled": "always",
 "sun.boot.library.path": "C:\\Program Files\\Java\\jdk1.8.0_101\\jre\\bin",
 "java.vm.version": "25.101-b13",
 "java.vm.vendor": "Oracle Corporation",
 "java.vendor.url": "http://java.oracle.com/",
 "java.rmi.server.randomIDs": "true",
 "path.separator": ";",
 "java.vm.name": "Java HotSpot(TM) 64-Bit Server VM",
 "file.encoding.pkg": "sun.io",
 "user.country": "CN",
 "user.script": "",
 "sun.java.launcher": "SUN_STANDARD",
 "sun.os.patch.level": "",
 "PID": "5268",
 "com.sun.management.jmxremote.port": "60093",
 "java.vm.specification.name": "Java Virtual Machine Spe
爲了避免敏感信息暴露到 /env ⾥,所有名爲 passwordsecretkey(或者名字中最後⼀段是這些)的屬性
/env ⾥都會加上“*”。舉個例⼦,如果有⼀個屬性名字是 database.password,那麼它在 /env 中的顯示效
果是這樣的:

/env/{name} ⽤法

就是 env 的擴展可以獲取指定配置信息,⽐如 http://localhost:8080/actuator/env/java.vm.version,返回
{"java.vm.version":"25.101-b13"}

heapdump

返回⼀個 GZip 壓縮的 JVM dump
啓動示例項⽬,訪問⽹址 http://localhost:8080/actuator/heapdump 會⾃動⽣成⼀個 JVM 的堆⽂件
heapdump,我們可以使⽤ JDK ⾃帶的 JVM 監控⼯具 VisualVM 打開此⽂件查看內存快照。類似如下圖:
 
 

httptrace

該端點⽤來返回基本的 HTTP 跟蹤信息。默認情況下,跟蹤信息的存儲採⽤
org.springframework.boot.actuate.trace.InMemoryTraceRepository 實現的內存⽅式,始終保留最近的 100
條請求記錄。
啓動示例項⽬,訪問⽹址 http://localhost:8080/actuator/httptrace,返回信息如下:
 
{
 "traces": [
 {
 "timestamp": "2018-11-21T12:42:25.333Z", 
 "principal": null, 
 "session": null, 
 "request": {
 "method": "GET", 
 "uri": "http://localhost:8080/actuator/heapdump", 
 "headers": {
 "cookie": [
 "Hm_lvt_0fb30c642c5f6453f17d881f529a1141=1513076406,151496
1720,1515649377; Hm_lvt_6d8e8bb59814010152d98507a18ad229=1515247964,1515296008,151
5672972,1516086283; UM_distinctid=1647364371ef6-003ab9d0469ea5-b7a103e-100200-1647
364371f104; CNZZDATA1260945749=232252692-1513233181-%7C1537492730"
 ], 
 "accept-language": [
 "zh-CN,zh;q=0.9"
 ], 
GitChat
 "upgrade-insecure-requests": [
 "1"
 ], 
 "host": [
 "localhost:8080"
 ], 
 "connection": [
 "keep-alive"
 ], 
 "accept-encoding": [
 "gzip, deflate, br"
 ], 
 "accept": [
 "text/html,application/xhtml+xml,application/xml;q=0.9,ima
ge/webp,image/apng,*/*;q=0.8"
 ], 
 "user-agent": [
 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537
.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36"
 ]
 }, 
 "remoteAddress": null
 }, 
 "response": {
 "status": 200, 
 "headers": {
 "Accept-Ranges": [
 "bytes"
 ], 
 "Content-Length": [
 "39454385"
 ], 
 "Date": [
 "Wed, 21 Nov 2018 12:42:25 GMT"
 ], 
 "Content-Type": [
 "application/octet-stream"
 ]
 }
 }, 
 "timeTaken": 1380
 }, 
 {
 ...
 }, 
 ...
 ]
}
記錄了請求的整個過程的詳細信息。

metrics

最重要的監控內容之⼀,主要監控了 JVM 內容使⽤、GC 情況、類加載信息等。
啓動示例項⽬,訪問⽹址 http://localhost:8080/actuator/metrics 返回部分信息如下:
{
 "mem": 337132,
 "mem.free": 183380,
 "processors": 4,
 "instance.uptime": 254552,
 "uptime": 259702,
 "systemload.average": -1.0,
 "heap.committed": 292864,
 "heap.init": 129024,
 "heap.used": 109483,
 "heap": 1827840,
 "nonheap.committed": 45248,
 "nonheap.init": 2496,
 "nonheap.used": 44269,
 "nonheap": 0,
 "threads.peak": 63,
 "threads.daemon": 43,
 "threads.totalStarted": 83,
 "threads": 46,
 "classes": 6357,
 "classes.loaded": 6357,
 "classes.unloaded": 0,
 "gc.ps_scavenge.count": 8,
 "gc.ps_scavenge.time": 99,
 "gc.ps_marksweep.count": 1,
 "gc.ps_marksweep.time": 43,
 "httpsessions.max": -1,
 "httpsessions.active": 0
}
/metrics 接⼝提供的信息進⾏簡單分類如下表:

分類

前綴

報告內容

 

垃圾收集器器

 

gc.*

已經發⽣生過的垃圾收集次數,以及垃圾收集所耗費的時間,適⽤用於標記—清理理垃圾收集器器和並⾏行行垃圾收集器器(數據源⾃自java.lang.management.GarbageCollectorMXBean)

 

內存

 

mem.*

分配給應⽤用程序的內存數量量和空閒的內存數量量(數據源⾃自 java.lang. Runtime)

heap.*

當前內存⽤用量量(數據源⾃自 java.lang.management.MemoryUsage)

類加載器器

 

classes.*

JVM 類加載器器加載與卸載的類的數量量(數據源⾃自 java.lang. management.ClassLoadingMXBean)

 

 

系統

processors、instance.uptime、uptime、systemload.average

 

系統信息,如處理理器器數量量(數據源⾃自        java.lang.Runtime、運⾏行行時間

(數據源⾃自    java.lang.management.RuntimeMXBean)、平均負載

(數據源⾃自 java.lang.management.OperatingSystemMXBean)

 

線程池

 

thread.*

線程、守護線程的數量量,以及 JVM 啓動後的線程數量量峯值(數據源

⾃ 自 java.lang .management.ThreadMXBean)

 

數據源

 

datasource.*

數據源連接的數量量(源⾃自數據源的元數據,僅當 Spring  應⽤用程序上下⽂文⾥裏里存在 DataSource Bean 的時候纔會有這個信息)

 

Tomcat

會話

 

httpsessions.*

Tomcat 的活躍會話數和最⼤大會話數(數據源⾃自嵌⼊入式 Tomcat 的Bean,僅在使⽤用嵌⼊入式 Tomcat 服務器器運⾏行行應⽤用程序時纔有這個信息)

 

HTTP

counter.status._、gauge.response._

 

多種應⽤用程序服務 HTTP 請求的度量量值與計數器器

解釋說明
  • 請注意,這⾥的⼀些度量值,⽐如數據源和 Tomcat 會話,僅在應⽤程序中運⾏特定組件時纔有數據, 還可以註冊⾃⼰的度量信息。
  • HTTP 的計數器和度量值需要做⼀點說明,counter.status 後的值是 HTTP 狀態碼,隨後是所請求的路 徑。舉個例⼦,counter.status.200.metrics 表明 /metrics 端點返回 200(OK) 狀態碼的次數。
  • HTTP 的度量信息在結構上也差不多,卻在報告另⼀類信息。它們全部以 gauge.response 開頭,表明 這是 HTTP 響應的度量信息,前綴後是對應的路徑,度量值是以毫秒爲單位的時間,反映了最近處理該 路徑請求的耗時。
  • 這⾥還有⼏個特殊的值需要注意,root 路徑指向的是根路徑或/star-star 代表了那些 Spring 認爲是靜 態資源的路徑,包括圖⽚、JavaScript 和樣式表,其中還包含了那些找不到的資源,這就是爲什麼經常 會看到 counter.status.404.star-star,因爲返回了 HTTP 404 (NOT FOUND) 狀態的請求數。  
  • /metrics 接⼝會返回所有的可⽤度量值,但你也可能只對某個值感興趣。要獲取單個值,請求時可以在GitChat URL 後加上對應的鍵名。例如,要查看空閒內存⼤⼩,可以向 /metrics/mem.free 發⼀個GET請求,⽐ 如訪問⽹址 http://localhost:8080/actuator/metrics/mem.free,返回:{"mem.free":178123}

shutdown

開啓接⼝優雅關閉 Spring Boot 應⽤,要使⽤這個功能⾸先需要在配置⽂件中開啓:
management.endpoint.shutdown.enabled=true
配置完成之後,啓動示例項⽬,使⽤ curl 模擬 post 請求訪問 shutdown 接⼝。
shutdown 接⼝默認只⽀持 post 請求。
curl -X POST "http://localhost:8080/actuator/shutdown" 
{
 "message": "Shutting down, bye..."
}
此時會發現應⽤已經被關閉。

mappings

描述全部的 URI 路徑,以及它們和控制器的映射關係。
啓動示例項⽬,訪問⽹址 http://localhost:8080/actuator/mappings 返回部分信息如下:
{
 "/**/favicon.ico": {
 "bean": "faviconHandlerMapping"
 },
 "{[/hello]}": {
 "bean": "requestMappingHandlerMapping",
 "method": "public java.lang.String com.neo.controller.HelloController.index()"
 },
 "{[/error]}": {
 "bean": "requestMappingHandlerMapping",
 "method": "public org.springframework.http.ResponseEntity<java.util.Map<java.l
ang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErr
orController.error(javax.servlet.http.HttpServletRequest)"
 }
}

threaddump

/threaddump 接⼝會⽣成當前線程活動的快照,這個功能⾮常好,⽅便我們在⽇常定位問題的時候查看線程GitChat
的情況,主要展示了線程名、線程 ID、線程的狀態、是否等待鎖資源等信息。
啓動示例項⽬,訪問⽹址 http://localhost:8080/actuator/threaddump 返回部分信息如下:

 {
 "threadName": "http-nio-8088-exec-6",
 "threadId": 49,
 "blockedTime": -1,
 "blockedCount": 0,
 "waitedTime": -1,
 "waitedCount": 2,
 "lockName": "java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionOb
ject@1630a501",
 "lockOwnerId": -1,
 "lockOwnerName": null,
 "inNative": false,
 "suspended": false,
 "threadState": "WAITING",
 "stackTrace": [
 {
 "methodName": "park",
 "fileName": "Unsafe.java",
 "lineNumber": -2,
 "className": "sun.misc.Unsafe",
 "nativeMethod": true
 },
 ...
 {
 "methodName": "run",
 "fileName": "TaskThread.java",
 "lineNumber": 61,
 "className": "org.apache.tomcat.util.threads.TaskThread$WrappingRunnable",
 "nativeMethod": false
 }
 ...
 ],
 "lockInfo": {
 "className": "java.util.concurrent.locks.AbstractQueuedSynchronizer$Conditio
nObject",
 "identityHashCode": 372286721
 }
 }
 ...
]
⽣產出現問題的時候,可以通過應⽤的線程快照來檢測應⽤正在執⾏的任務。GitChat

總結

經過這節課實踐我們發現 Spring Boot Actuator 作爲 Spring Boot ⾃帶的監控組件⾮常強⼤,它可以監控和管
Spring Boot 應⽤,如健康檢查、審計、統計和 HTTP 追蹤等,所有的這些特性可以通過 JMX 或者 HTTP
endpoints 來獲得。使⽤ Spring Boot Actuator 之後,可以讓我們通過很簡單的接⼝或者命令來查詢應⽤的⼀
切運⾏狀態,從⽽達到對應⽤的運⾏情況瞭如指掌。
 
 
發佈了89 篇原創文章 · 獲贊 19 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章