Quartz 教程

一诡曙、關(guān)于 Quartz

Quartz logo
  • Quartz 是一個(gè)完全由 Java 編寫的開源作業(yè)調(diào)度框架,為在 Java 應(yīng)用程序中進(jìn)行作業(yè)調(diào)度提供了簡(jiǎn)單卻強(qiáng)大的機(jī)制笆豁。
  • Quartz 可以與 J2EE 與 J2SE 應(yīng)用程序相結(jié)合也可以單獨(dú)使用郎汪。
  • Quartz 允許程序開發(fā)人員根據(jù)時(shí)間的間隔來調(diào)度作業(yè)定欧。
  • Quartz 實(shí)現(xiàn)了作業(yè)和觸發(fā)器的多對(duì)多的關(guān)系,還能把多個(gè)作業(yè)與不同的觸發(fā)器關(guān)聯(lián)怒竿。

二、Quartz 核心概念

核心組件
  • Scheduler:調(diào)度容器
  • Job:Job接口類扩氢,即被調(diào)度的任務(wù)
  • JobDetail :Job的描述類耕驰,job執(zhí)行時(shí)的依據(jù)此對(duì)象的信息反射實(shí)例化出Job的具體執(zhí)行對(duì)象。
  • Trigger:觸發(fā)器录豺,存放Job執(zhí)行的時(shí)間策略朦肘。用于定義任務(wù)調(diào)度時(shí)間規(guī)則。
  • JobStore: 存儲(chǔ)作業(yè)和調(diào)度期間的狀態(tài)
  • Calendar:指定排除的時(shí)間點(diǎn)(如排除法定節(jié)假日)

job

Job 是一個(gè)接口双饥,只有一個(gè)方法 void execute(JobExecutionContext context)媒抠,開發(fā)者實(shí)現(xiàn)接口來定義任務(wù)。JobExecutionContext 類提供了調(diào)度上下文的各種信息咏花。Job 運(yùn)行時(shí)的信息保存在 JobDataMap 實(shí)例中趴生。例如:

public class HelloJob implements BaseJob {
    private static Logger _log = LoggerFactory.getLogger(HelloJob.class);  
    public HelloJob() { }  
    public void execute(JobExecutionContext context) throws JobExecutionException {
        _log.error("Hello Job執(zhí)行時(shí)間: " + new Date());
    }
}  

JobDetailImpl 類 / JobDetail 接口

JobDetailImpl類實(shí)現(xiàn)了JobDetail接口,用來描述一個(gè) job昏翰,定義了job所有屬性及其 get/set 方法苍匆。下面是 job 內(nèi)部的主要屬性:

屬性名 說明
class 必須是job實(shí)現(xiàn)類(比如JobImpl),用來綁定一個(gè)具體job
name job 名稱棚菊。如果未指定浸踩,會(huì)自動(dòng)分配一個(gè)唯一名稱。所有job都必須擁有一個(gè)唯一name统求,如果兩個(gè) job 的name重復(fù)检碗,則只有最前面的 job 能被調(diào)度
group job 所屬的組名
description job描述
durability 是否持久化。如果job設(shè)置為非持久码邻,當(dāng)沒有活躍的trigger與之關(guān)聯(lián)的時(shí)候折剃,job 會(huì)自動(dòng)從scheduler中刪除。也就是說像屋,非持久job的生命期是由trigger的存在與否決定的
shouldRecover 是否可恢復(fù)微驶。如果 job 設(shè)置為可恢復(fù),一旦 job 執(zhí)行時(shí)scheduler發(fā)生hard shutdown(比如進(jìn)程崩潰或關(guān)機(jī))开睡,當(dāng)scheduler重啟后因苹,該job會(huì)被重新執(zhí)行
jobDataMap 除了上面常規(guī)屬性外,用戶可以把任意kv數(shù)據(jù)存入jobDataMap篇恒,實(shí)現(xiàn) job 屬性的無限制擴(kuò)展扶檐,執(zhí)行 job 時(shí)可以使用這些屬性數(shù)據(jù)。此屬性的類型是JobDataMap胁艰,實(shí)現(xiàn)了Serializable接口款筑,可做跨平臺(tái)的序列化傳輸

Trigger

是一個(gè)類智蝠,描述觸發(fā)Job執(zhí)行的時(shí)間觸發(fā)規(guī)則。主要有 SimpleTriggerCronTrigger 這兩個(gè)子類奈梳。當(dāng)僅需觸發(fā)一次或者以固定時(shí)間間隔周期執(zhí)行杈湾,SimpleTrigger是最適合的選擇;而CronTrigger則可以通過Cron表達(dá)式定義出各種復(fù)雜時(shí)間規(guī)則的調(diào)度方案:如每早晨9:00執(zhí)行攘须,周一漆撞、周三、周五下午5:00執(zhí)行等于宙;

以下是 trigger 的屬性:

屬性名 屬性類型 說明
name 所有trigger通用 trigger名稱
group 所有trigger通用 trigger所屬的組名
description 所有trigger通用 trigger描述
calendarName 所有trigger通用 日歷名稱浮驳,指定使用哪個(gè)Calendar類,經(jīng)常用來從trigger的調(diào)度計(jì)劃中排除某些時(shí)間段
misfireInstruction 所有trigger通用 錯(cuò)過job(未在指定時(shí)間執(zhí)行的job)的處理策略捞魁,默認(rèn)為MISFIRE_INSTRUCTION_SMART_POLICY至会。詳見這篇blog^Quartz misfire
priority 所有trigger通用 優(yōu)先級(jí),默認(rèn)為5谱俭。當(dāng)多個(gè)trigger同時(shí)觸發(fā)job時(shí)奉件,線程池可能不夠用,此時(shí)根據(jù)優(yōu)先級(jí)來決定誰先觸發(fā)
jobDataMap 所有trigger通用 同job的jobDataMap昆著。假如job和trigger的jobDataMap有同名key瓶蚂,通過getMergedJobDataMap()獲取的jobDataMap,將以trigger的為準(zhǔn)
startTime 所有trigger通用 觸發(fā)開始時(shí)間宣吱,默認(rèn)為當(dāng)前時(shí)間窃这。決定什么時(shí)間開始觸發(fā)job
endTime 所有trigger通用 觸發(fā)結(jié)束時(shí)間。決定什么時(shí)間停止觸發(fā)job
nextFireTime SimpleTrigger私有 下一次觸發(fā)job的時(shí)間
previousFireTime SimpleTrigger私有 上一次觸發(fā)job的時(shí)間
repeatCount SimpleTrigger私有 需觸發(fā)的總次數(shù)
timesTriggered SimpleTrigger私有 已經(jīng)觸發(fā)過的次數(shù)
repeatInterval SimpleTrigger私有 觸發(fā)間隔時(shí)間

Calendar

org.quartz.Calendarjava.util.Calendar不同征候,它是一些日歷特定時(shí)間點(diǎn)的集合(可以簡(jiǎn)單地將org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一個(gè)日歷時(shí)間點(diǎn)杭攻,無特殊說明后面的Calendar即指org.quartz.Calendar)。一個(gè)Trigger可以和多個(gè)Calendar關(guān)聯(lián)疤坝,以便排除或包含某些時(shí)間點(diǎn)兆解。假設(shè),我們安排每周星期一早上10:00執(zhí)行任務(wù)跑揉,但是如果碰到法定的節(jié)日锅睛,任務(wù)則不執(zhí)行,這時(shí)就需要在Trigger觸發(fā)機(jī)制的基礎(chǔ)上使用Calendar進(jìn)行定點(diǎn)排除历谍。

Scheduler

調(diào)度器现拒,代表一個(gè)Quartz的獨(dú)立運(yùn)行容器,好比一個(gè)『大管家』望侈,這個(gè)大管家應(yīng)該可以接受 Job印蔬, 然后按照各種Trigger去運(yùn)行,TriggerJobDetail可以注冊(cè)到Scheduler中脱衙,兩者在Scheduler中擁有各自的組及名稱侥猬,組及名稱是Scheduler查找定位容器中某一對(duì)象的依據(jù)例驹,Trigger的組及名稱必須唯一,JobDetail的組和名稱也必須唯一(但可以和Trigger的組和名稱相同退唠,因?yàn)樗鼈兪遣煌愋偷模┚樾狻cheduler定義了多個(gè)接口方法,允許外部通過組及名稱訪問和控制容器中Trigger和JobDetail瞧预。

image

Scheduler 可以將 Trigger 綁定到某一 JobDetail 中屎债,這樣當(dāng) Trigger 觸發(fā)時(shí),對(duì)應(yīng)的 Job 就被執(zhí)行松蒜。可以通過 SchedulerFactory創(chuàng)建一個(gè) Scheduler 實(shí)例已旧。Scheduler 擁有一個(gè) SchedulerContext秸苗,它類似于 ServletContext,保存著 Scheduler 上下文信息运褪,Job 和 Trigger 都可以訪問 SchedulerContext 內(nèi)的信息惊楼。SchedulerContext 內(nèi)部通過一個(gè) Map,以鍵值對(duì)的方式維護(hù)這些上下文數(shù)據(jù)秸讹,SchedulerContext 為保存和獲取數(shù)據(jù)提供了多個(gè) put() 和 getXxx() 的方法檀咙。可以通過Scheduler# getContext()獲取對(duì)應(yīng)的SchedulerContext實(shí)例璃诀;

ThreadPool

Scheduler 使用一個(gè)線程池作為任務(wù)運(yùn)行的基礎(chǔ)設(shè)施弧可,任務(wù)通過共享線程池中的線程提高運(yùn)行效率。

進(jìn)行一個(gè)定時(shí)任務(wù)的簡(jiǎn)單實(shí)例

public class JobTest implements BaseJob {
    private static org.slf4j.Logger log = LoggerFactory.getLogger(JobTest.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        log.error("JobTest 執(zhí)行時(shí)間: " + new Date());
    }
}
@Test
public void quartzTest() throws SchedulerException{
    // 1. 創(chuàng)建 SchedulerFactory
    SchedulerFactory factory = new StdSchedulerFactory();
    // 2. 從工廠中獲取調(diào)度器實(shí)例
    Scheduler scheduler = factory.getScheduler();

    // 3. 引進(jìn)作業(yè)程序
    JobDetail jobDetail = JobBuilder.newJob(JobTest.class).withDescription("this is a ram job") //job的描述
            .withIdentity("jobTest", "jobTestGrip") //job 的name和group
            .build();

    long time=  System.currentTimeMillis() + 3*1000L; //3秒后啟動(dòng)任務(wù)
    Date statTime = new Date(time);

    // 4. 創(chuàng)建Trigger
    //使用SimpleScheduleBuilder或者CronScheduleBuilder
    Trigger trigger = TriggerBuilder.newTrigger()
            .withDescription("this is a cronTrigger")
            .withIdentity("jobTrigger", "jobTriggerGroup")
            //.withSchedule(SimpleScheduleBuilder.simpleSchedule())
            .startAt(statTime)  //默認(rèn)當(dāng)前時(shí)間啟動(dòng)
            .withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")) //兩秒執(zhí)行一次
            .build();

    // 5. 注冊(cè)任務(wù)和定時(shí)器
    scheduler.scheduleJob(jobDetail, trigger);

    // 6. 啟動(dòng) 調(diào)度器
    scheduler.start();
    _log.info("啟動(dòng)時(shí)間 : " + new Date());
}

三劣欢、Quartz 設(shè)計(jì)分析

quartz.properties文件

Quartz 有一個(gè)叫做quartz.properties的配置文件棕诵,它允許你修改框架運(yùn)行時(shí)環(huán)境。缺省是使用 Quartz.jar 里面的quartz.properties 文件凿将。你應(yīng)該創(chuàng)建一個(gè) quartz.properties 文件的副本并且把它放入你工程的 classes 目錄中以便類裝載器找到它校套。

// 調(diào)度標(biāo)識(shí)名 集群中每一個(gè)實(shí)例都必須使用相同的名稱 (區(qū)分特定的調(diào)度器實(shí)例) 
org.quartz.scheduler.instanceName:DefaultQuartzScheduler 
// ID設(shè)置為自動(dòng)獲取 每一個(gè)必須不同 (所有調(diào)度器實(shí)例中是唯一的) 
org.quartz.scheduler.instanceId :AUTO 
// 數(shù)據(jù)保存方式為持久化 
org.quartz.jobStore.class :org.quartz.impl.jdbcjobstore.JobStoreTX 
// 表的前綴 
org.quartz.jobStore.tablePrefix : QRTZ_ 
// 設(shè)置為TRUE不會(huì)出現(xiàn)序列化非字符串類到 BLOB 時(shí)產(chǎn)生的類版本問題 
// org.quartz.jobStore.useProperties : true 
// 加入集群 true 為集群 false不是集群 
org.quartz.jobStore.isClustered : false 
// 調(diào)度實(shí)例失效的檢查時(shí)間間隔 
org.quartz.jobStore.clusterCheckinInterval:20000 
// 容許的最大作業(yè)延長(zhǎng)時(shí)間 
org.quartz.jobStore.misfireThreshold :60000 
// ThreadPool 實(shí)現(xiàn)的類名 
org.quartz.threadPool.class:org.quartz.simpl.SimpleThreadPool 
// 線程數(shù)量 
org.quartz.threadPool.threadCount : 10 
// 線程優(yōu)先級(jí) 
// threadPriority 屬性的最大值是常量 java.lang.Thread.MAX_PRIORITY,等于10牧抵。最小值為常量 java.lang.Thread.MIN_PRIORITY笛匙,為1
org.quartz.threadPool.threadPriority : 5
// 自創(chuàng)建父線程 
//org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true 
// 數(shù)據(jù)庫別名 
org.quartz.jobStore.dataSource : qzDS 
// 設(shè)置數(shù)據(jù)源 
org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver 
org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz 
org.quartz.dataSource.qzDS.user:root 
org.quartz.dataSource.qzDS.password:123456 
org.quartz.dataSource.qzDS.maxConnection:10

Quartz 調(diào)度器

Quartz框架的核心是調(diào)度器。調(diào)度器負(fù)責(zé)管理Quartz應(yīng)用運(yùn)行時(shí)環(huán)境犀变。啟動(dòng)時(shí)妹孙,框架初始化一套worker線程,這套線程被調(diào)度器用來執(zhí)行預(yù)定的作業(yè)获枝。這就是 Quartz 怎樣能并發(fā)運(yùn)行多個(gè)作業(yè)的原理记劈。Quartz 依賴一套松耦合的線程池管理部件來管理線程環(huán)境务荆。

兩種作業(yè)存儲(chǔ)方式

1. RAMJobStore

- 通常的內(nèi)存來持久化調(diào)度程序信息。這種作業(yè)存儲(chǔ)類型最容易配置馆衔、構(gòu)造和運(yùn)行。
- 因?yàn)檫@種方式的調(diào)度程序信息是被分配到 JVM 內(nèi)存中嘹裂,所以,當(dāng)應(yīng)用程序停止運(yùn)行時(shí),所有調(diào)度信息將被丟失旭旭。如果你需要在重新啟動(dòng)之間持久化調(diào)度信息,則將需要第二種類型的作業(yè)存儲(chǔ)葱跋。 

2. JDBC作業(yè)存儲(chǔ)

- 需要JDBC驅(qū)動(dòng)程序和后臺(tái)數(shù)據(jù)庫來持久化調(diào)度程序信息(支持集群)
表關(guān)系和解釋
表關(guān)系
表名稱 | 說明
qrtz_blob_triggers | Trigger作為Blob類型存儲(chǔ)(用于Quartz用戶用JDBC創(chuàng)建他們自己定制的Trigger類型持寄,JobStore 并不知道如何存儲(chǔ)實(shí)例的時(shí)候)
qrtz_calendars | 以Blob類型存儲(chǔ)Quartz的Calendar日歷信息, quartz可配置一個(gè)日歷來指定一個(gè)時(shí)間范圍
qrtz_cron_triggers | 存儲(chǔ)Cron Trigger娱俺,包括Cron表達(dá)式和時(shí)區(qū)信息稍味。
qrtz_fired_triggers |   存儲(chǔ)與已觸發(fā)的Trigger相關(guān)的狀態(tài)信息,以及相聯(lián)Job的執(zhí)行信息
qrtz_job_details | 存儲(chǔ)每一個(gè)已配置的Job的詳細(xì)信息
qrtz_locks |    存儲(chǔ)程序的非觀鎖的信息(假如使用了悲觀鎖)
qrtz_paused_trigger_graps |     存儲(chǔ)已暫停的Trigger組的信息
qrtz_scheduler_state |  存儲(chǔ)少量的有關(guān) Scheduler的狀態(tài)信息荠卷,和別的 Scheduler 實(shí)例(假如是用于一個(gè)集群中)
qrtz_simple_triggers     | 存儲(chǔ)簡(jiǎn)單的 Trigger模庐,包括重復(fù)次數(shù),間隔油宜,以及已觸的次數(shù)
qrtz_triggers    | 存儲(chǔ)已配置的 Trigger的信息
qrzt_simprop_triggers   

利用 SpringBoot + Quartz 搭建的界面化的 Demo

在網(wǎng)上找到一個(gè)搭好的 Demo掂碱,感謝大神!原文: Spring Boot集成持久化Quartz定時(shí)任務(wù)管理和界面展示

本工程所用到的技術(shù)或工具

Spring Boot
Mybatis
Quartz
PageHelper
VueJS
ElementUI
MySql數(shù)據(jù)庫

先看圖:

效果圖.png

新建任務(wù).png

源碼地址

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末慎冤,一起剝皮案震驚了整個(gè)濱河市疼燥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蚁堤,老刑警劉巖醉者,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異披诗,居然都是意外死亡湃交,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門藤巢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搞莺,“玉大人,你說我怎么就攤上這事掂咒〔挪祝” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵绍刮,是天一觀的道長(zhǎng)温圆。 經(jīng)常有香客問我,道長(zhǎng)孩革,這世上最難降的妖魔是什么岁歉? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮膝蜈,結(jié)果婚禮上锅移,老公的妹妹穿的比我還像新娘熔掺。我一直安慰自己,他們只是感情好非剃,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布置逻。 她就那樣靜靜地躺著,像睡著了一般备绽。 火紅的嫁衣襯著肌膚如雪券坞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天肺素,我揣著相機(jī)與錄音恨锚,去河邊找鬼。 笑死倍靡,一個(gè)胖子當(dāng)著我的面吹牛猴伶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播菌瘫,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼蜗顽,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼布卡!你這毒婦竟也來了雨让?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤忿等,失蹤者是張志新(化名)和其女友劉穎栖忠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贸街,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡庵寞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了薛匪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捐川。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖逸尖,靈堂內(nèi)的尸體忽然破棺而出古沥,到底是詐尸還是另有隱情,我是刑警寧澤娇跟,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布岩齿,位于F島的核電站,受9級(jí)特大地震影響苞俘,放射性物質(zhì)發(fā)生泄漏盹沈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一吃谣、第九天 我趴在偏房一處隱蔽的房頂上張望乞封。 院中可真熱鬧做裙,春花似錦、人聲如沸歌亲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陷揪。三九已至惋鸥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間悍缠,已是汗流浹背卦绣。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留飞蚓,地道東北人滤港。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像趴拧,于是被迫代替她去往敵國和親溅漾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 概述 了解Quartz體系結(jié)構(gòu) Quartz對(duì)任務(wù)調(diào)度的領(lǐng)域問題進(jìn)行了高度的抽象著榴,提出了調(diào)度器添履、任務(wù)和觸發(fā)器這3個(gè)...
    張晨輝Allen閱讀 2,218評(píng)論 2 11
  • 什么是定時(shí)任務(wù)調(diào)度 基于給定的時(shí)間點(diǎn),給定的時(shí)間間隔或者給定的執(zhí)行次數(shù)自動(dòng)完成執(zhí)行任務(wù) 在Java中的定時(shí)調(diào)度工具...
    Hey_Shaw閱讀 2,483評(píng)論 2 1
  • 什么是Quartz ??Quartz是OpenSymphony開源組織在Job scheduling領(lǐng)域的開源項(xiàng)目...
  • 額脑又,昨天來是吧……好像是這個(gè)樣子 不行了暮胧,明天要把衣服洗了,不能一直玩问麸,玩瘋了往衷,洗衣服,這是明天的必要任務(wù)严卖,記住跋帷!O省来颤!
    藍(lán)道閱讀 105評(píng)論 0 0
  • 作業(yè)再次寫到了十一點(diǎn)半。天天打疲勞戰(zhàn)疟呐,身體和精神都怎么能受得了脚曾?這真是小洞不補(bǔ),大洞吃苦启具。遠(yuǎn)從幼兒園大班開...
    海闊林韻閱讀 192評(píng)論 1 0