第 7 章:第七章:星期六——災難
早上十點零三分,促銷活動的第一波流量灌進來的時候,志明正站在便利商店的微波爐前面,盯著他的雞腿便當在裡面轉。
「叮。」
他拿出便當,塑膠袋在底部凝了一層水蒸氣。他沒有買咖啡。昨晚只睡了三個小時,但他不想再讓咖啡因碰自己的胃。不是因為養生,是因為他感覺自己的胃已經縮得太緊,不需要再有任何東西去擠它。
到辦公室的時候是十點二十分。辦公室裡的人比平常星期六多了一倍——促銷日是全員待命的日子,雖然工程部門不需要全員到齊,但副總上週說了「希望大家共體時艱」。共體時艱的意思就是,你可以加班,但午餐補助只報八十塊。
「Zhi哥早。」小琪已經坐在位子上了,面前的螢幕開著 GA 即時看板。她畫了淡妝,志明看見她的眼影是亮片的。他不知道為什麼她今天特地打扮。「流量開始衝了,目前小時量大概是平常的五倍。」
「嗯。」志明把便當放在桌上,打開筆電。
Horizon dashboard 立刻跳出來。他昨晚離開公司的時候把這個頁面開著,筆電的亮度在暗夜裡顯得刺眼。現在是白天,窗外的陽光把螢幕照得幾乎看不見,他把手遮在上面,瞇著眼讀數據。
Jobs processed: 2,847。Failed: 0。Avg wait time: 1.8s。
Avg wait time 從昨天的 1.2s 變成了 1.8s,但還在合理範圍。Queue worker 處理每個 job 的時間取決於 job 的複雜度——OrderConfirmationEmail 這種寄信 job 通常不到一秒,但如果有大量的 webhook callback 同時進來,worker 就需要排隊。
志明看著那個 1.8s,想起昨晚凌晨 log 裡面那三次 QueueWorkerTimeout。WARNING,不是 ERROR。他告訴自己那只是 SMTP 偶爾慢。但現在是早上十點,促銷剛開始,Avg wait time 就已經是昨晚的兩倍了。
如果流量繼續往上走——
他打斷自己的念頭,打開 log。
$ tail -f laravel.log | grep "PaymentWebhookReceived"
log 流出來的速度比昨天快了不少。每一行之間的間隔從兩三秒變成了不到一秒。
[2024-06-16 10:22:03] production.INFO: PaymentWebhookReceived {"order_id":892,"status":"success","signature":"valid"}
[2024-06-16 10:22:03] production.INFO: PaymentWebhookReceived {"order_id":893,"status":"success","signature":"valid"}
[2024-06-16 10:22:04] production.INFO: PaymentWebhookReceived {"order_id":894,"status":"success","signature":"signature":"valid"}
[2024-06-16 10:22:04] production.INFO: PaymentWebhookReceived {"order_id":895,"status":"success","signature":"valid"}
四筆 webhook 在兩秒內進來。高併發。他盯著螢幕,看後面的 log 是不是跟著 OrderStatusUpdated。
[2024-06-16 10:22:03] production.INFO: OrderStatusUpdated {"order_id":892,"from":"pending","to":"paid"}
[2024-06-16 10:22:03] production.INFO: OrderStatusUpdated {"order_id":893,"from":"pending","to":"paid"}
[2024-06-16 10:22:04] production.INFO: OrderStatusUpdated {"order_id":894,"from":"pending","to":"paid"}
[2024-06-16 10:22:04] production.INFO: OrderStatusUpdated {"order_id":895,"from":"pending","to":"paid"}
正常的。每一筆 webhook 進來,Controller 按照順序處理——查訂單、驗 signature、更新狀態、dispatch event。
他呼了一口氣,打開便當,開始吃。雞腿已經有點涼了,但他還是吃了。不是因為餓,是因為他知道自己今天不會有時間好好吃一頓飯。這是他從以前加班學到的經驗:你不吃的東西,你的身體會自己找你討回來。
十一點零八分。
Slack 上客服主管傳了一個訊息,不是在 #project-payment-gateway,而是直接私訊他:
> 志明,客服剛收到一通客訴,客戶說他被重複扣款了。可以提供一下 log 嗎?我確認一下是不是系統問題。
志明的手停在鍵盤上。
他看著這則訊息,看了大概五秒。然後切到 Terminal。
$ grep "duplicate\| Duplicate\|double\|Double" laravel.log
沒有結果。
他再跑一次,搜尋客服提供的訂單編號:
$ grep "ORD-20240615-10847" laravel.log
兩筆紀錄:
[2024-06-16 11:02:15] production.INFO: PaymentWebhookReceived {"order_id":10847,"status":"success","signature":"valid"}
[2024-06-16 11:02:15] production.INFO: OrderStatusUpdated {"order_id":10847,"from":"pending","to":"paid"}
[2024-06-16 11:02:16] production.INFO: PaymentWebhookReceived {"order_id":10847,"status":"success","signature":"valid"}
[2024-06-16 11:02:16] production.INFO: OrderStatusUpdated {"order_id":10847,"from":"paid","to":"paid"}
同一個 order_id,兩筆 PaymentWebhookReceived,間隔一秒。
第一筆把狀態從 pending 改成 paid。第二筆把狀態從 paid 改成 paid——看起來沒有差別,但問題出在金流那端。金流的 webhook 是通知「付款成功」,而 PaymentWebhookController 在收到通知的時候,會觸發 PaymentCompletedEvent,而這個 event 的 listener 會呼叫金流的請款 API。
如果同一個訂單的 webhook 被處理了兩次,請款 API 就會被呼叫兩次。
兩次請款。兩次扣款。
志明把 Terminal 的畫面截圖,傳給客服主管。然後他打開 PaymentWebhookController.php,找到那段他看了無數次的 code:
public function handle(Request $request)
{
$order = Order::where('transaction_id', $request->transaction_id)->first();
if (!$order || $order->status === 'paid') {
return response()->json(['message' => 'Order already processed']);
}
$order->status = 'paid';
$order->save();
event(new PaymentCompletedEvent($order));
return response()->json(['message' => 'OK']);
}
他看著第 67 行的 $order = Order::where('transaction_id', $request->transaction_id)->first();。
沒有 lock。
如果兩個 webhook 同時進來——不是「間隔一秒」,而是「同一個 millisecond」——兩個 request 都會在第 67 行查到同一個 $order,而且 $order->status 都還是 pending。因為兩個 request 都還沒執行到 $order->status = 'paid' 那一行。
兩個 request 都通過了 if (!$order || $order->status === 'paid') 的檢查。
兩個 request 都執行了 $order->status = 'paid' 和 $order->save()。
兩個 request 都 dispatch 了 PaymentCompletedEvent。
Race Condition。
他上週在 GitHub Issue #247 上看到的那個 Race Condition。
志明把椅子往後靠,閉上眼睛。
他想到的第一件事不是「怎麼修」,而是「有多少人」。
他打開資料庫,跑了一個查詢:
SELECT COUNT(*) as duplicate_count
FROM payment_logs
WHERE created_at > '2024-06-16 10:00:00'
GROUP BY order_id
HAVING COUNT(*) > 1;
結果:1。
目前只有一個。
他傳訊息給客服主管:「目前系統 log 顯示有一筆重複的 webhook callback,已確認是 Race Condition 的問題。請幫我先確認客戶的退款流程,我這邊先處理技術面。」
客服主管回了「好」。
志明站起來,走到茶水間,倒了一杯水。他站在飲水機前面,手握著紙杯,水滿了也沒有移開。水溢出來,流到他的手上。
他想到上週四,大衛在廁所裡眼睛紅紅的樣子。
他想到上週三,老張在茶水間說:「我只是不想收拾殘局。」
他想到上週一,大衛在會議上說:「可是那個 Package 好像不太穩定……」
他想到自己說:「先用了,有問題再說。」
他把水倒掉,走回位子。
十一點三十一分。
Slack 上客服主管又傳來了:
> 又來了。第二通。
志明的手指在鍵盤上停了一下。然後他開始打字:
SELECT COUNT(*) as duplicate_count
FROM payment_logs
WHERE created_at > '2024-06-16 10:00:00'
GROUP BY order_id
HAVING COUNT(*) > 1;
結果:3。
從 1 變成 3,用了二十三分鐘。
他打開 Horizon dashboard。Avg wait time: 3.4s。Jobs processed: 12,403。Failed: 0。
Avg wait time 從 1.8s 變成了 3.4s。Queue 在拉長。webhook 進來得越來越快,worker 來不及消化。而 worker 越慢,webhook 就越容易堆積,堆積越高,併發的機率就越大,Race Condition 被觸發的機率就越大。
正向迴圈。但不是好的那種。
他打開 Terminal,開始寫 fix。不是規劃,是直接寫。他的手在鍵盤上跑,像是比他的腦還快:
public function handle(Request $request)
{
return DB::transaction(function () use ($request) {
$order = Order::where('transaction_id', $request->transaction_id)
->lockForUpdate()
->first();
if (!$order || $order->status === 'paid') {
return response()->json(['message' => 'Order already processed']);
}
$order->status = 'paid';
$order->save();
event(new PaymentCompletedEvent($order));
return response()->json(['message' => 'OK']);
});
}
DB::transaction() 包起來,加上 lockForUpdate()。SELECT ... FOR UPDATE 會在 transaction 內對該行加上 row-level lock,其他 concurrent request 必須等到這個 transaction 完成才能讀取同一行。這樣就不會有兩個 request 同時讀到 status = 'pending 的情況。
他寫完,看了三秒。
然後他打開 GitHub,開了一個新的 branch:hotfix/race-condition-lock。
他沒有直接 deploy。他跑了一次本地測試,確認沒有 syntax error。然後他打開 Slack,傳訊息給團隊:
> 各位,我發現 Race Condition 的問題已經在 production 上發生了,目前有少量重複扣款的案例。我已經寫好 fix,正在測試,預計今天下午可以部署。
大衛秒回:
> 需要我幫忙什麼?
小琪也回了:
> 天啊……Zhi哥對不起,我前端那邊——
志明打斷她:
> 不是前端的問題,是後端 API 層的。你不用道歉。
他打完這行字,看了一眼老張的位子。老張今天有來,坐在那裡,保溫杯打開著,正在喝茶。他沒有看 Slack,或者看了但沒有回。
十二點十五分。
客服主管傳來了第三通、第四通、第五通客訴。
志明看著 Slack 上的訊息,每一條都像一顆小石子丟進水裡,漣漪慢慢擴大。
他決定不等了。他把 fix merge 到 main,跑了一次 deploy script。
$ bash deploy.sh
六個步驟。四十秒。
deploy 完成之後,他打開 log,觀察新的 webhook 處理流程:
[2024-06-16 12:18:33] production.INFO: PaymentWebhookReceived {"order_id":11203,"status":"success","signature":"valid"}
[2024-06-16 12:18:33] production.INFO: OrderStatusUpdated {"order_id":11203,"from":"pending","to":"paid"}
[2024-06-16 12:18:33] production.INFO: PaymentCompletedEventDispatched {"order_id":11203}
[2024-06-16 12:18:34] production.INFO: PaymentWebhookReceived {"order_id":11204,"status":"success","signature":"valid"}
[2024-06-16 12:18:34] production.INFO: OrderStatusUpdated {"order_id":11204,"from":"pending","to":"paid"}
正常。
他等了三分鐘,跑了一次 SQL 查詢:
SELECT COUNT(*) as duplicate_count
FROM payment_logs
WHERE created_at > '2024-06-16 12:15:00'
GROUP BY order_id
HAVING COUNT(*) > 1;
結果:0。
Fix 有效。
他靠回椅背上,閉上眼睛。
但問題已經發生了。在 deploy 之前,有多少筆重複扣款?
他查了一下:從十點到十二點十五分,總共 37 筆重複扣款。37 個人,每個人被多扣了一筆訂單的金額。金額從 150 元到 4,200 元不等。
37 筆。
他傳訊息給客服主管:「我已經部署了 fix,後續應該不會再發生。之前重複扣款的案例,請幫我整理一份清單,我需要確認退款流程。」
客服主管回了一個 OK 的手勢。
志明把手機放在桌上,雙手搓了搓臉。
他沒有注意到副總什麼時候站在他身後。
「志明。」
志明轉頭。林副總站在那裡,淺藍色的襯衫,袖子捲到肘間,表情不是憤怒,是一種更難應付的東西——平靜。一種經過計算的平靜。
「副總。」
「來一下會議室。」
不是問句。
志明站起來,跟著副總走。經過小琪位子的時候,小琪抬頭看了他一眼,嘴巴動了一下,沒有發出聲音。大衛低著頭,手指在鍵盤上沒有動。老張的保溫杯蓋上了,他看著志明和副總的背影,表情像是在看一齣他已經知道結局的戲。
會議室裡已經坐了四個人。
副總、客服主管、公關主管、還有黃經理——人資的黃經理。志明上次看到黃經理是在年初的性平教育訓練上,她站在會議室前面放投影片,用一種很平穩的語氣念:「職場霸凌的定義包括但不限於……」
志明坐下來。
副總先開口:「志明,目前的情況,你先說明一下。」
志明清了清喉嚨。他的聲音比他預想的還要乾:「今天促銷活動開始之後,大約十點左右,系統開始收到大量的金流 webhook callback。因為之前使用的第三方 Package 在處理 webhook 時沒有加入 database lock,在高併發的情況下,同一個訂單的 webhook 可能被同時處理兩次,導致重複請款。我已經在十二點十五分部署了 fix,目前問題已經控制住。在 fix 之前,總共有 37 筆重複扣款。」
他停了一下。
「37 筆的金額已經在跟客服確認退款流程,預計今天內可以完成退款。」
客服主管點了一下頭,在紙上寫了什麼。
公關主管推了推眼鏡:「37 筆,目前還沒有媒體報導。但如果有人去 PTT 或 Mobile01 爆料,我們需要準備一份聲明。」
副總沒有看公關主管。他看著志明。
「志明,我問一個問題。」副總的語速比平常慢,像是在對一個不太聰明的人解釋事情。「這個 Race Condition 的問題,之前有沒有人提出過?」
志明愣了不到一秒。但那一秒裡,很多東西閃過去了。
大衛在會議上舉手說「這個 Package 好像不太穩定」。
老張在茶水間說「你那個 PaymentService,有處理 Race Condition 嗎」。
小琪在測試時發現重複點擊的問題。
大衛在測試報告最後一頁用小小的字寫著「注意:重複點擊可能導致重複訂單」。
「有。」志明說。
「那為什麼沒有測到?」
副總的語氣沒有升高。他不需要升高。這個問題本身就是一個形狀——一個把所有空氣從會議室裡抽走的形狀。
志明沉默。
他不知道該怎麼回答。說「我沒有仔細看測試報告」?說「我當時覺得前端 debounce 就夠了」?說「我當時怕要求延期會讓副總覺得我扛不住」?
每一個答案都是對的。每一個答案也都是錯的。
「我當時判斷前端加入 debounce 可以緩解這個問題,」志明說,他的聲音很平,像在念一段他寫好的稿,「但我沒有考慮到 webhook 本身會在高併發下被重複觸發。這是我的判斷失誤。」
副總點了一下頭。那個點頭很輕,像在說「好,那就這樣了」。
然後他轉向會議室裡的其他人:「各位,這個技術選型是志明全權決定的。我當時充分信任他的專業判斷。」
信任。
志明坐在會議室裡,聽著這句話,感覺空氣被抽走。
他看過這句話。在第一章的閃回裡,在凌晨三點的客廳地板上,在啤酒罐和筆電螢幕的光之間,他看過這句話。但那時候是記憶,是已經發生的事情的回聲。現在是現場。副總的嘴唇在動,聲音從他的嘴裡出來,經過會議室的空氣,到達志明的耳朵。
信任。
這個詞怎麼聽起來這麼重。
不是因為副總說錯了。副總沒有說錯。技術選型確實是志明決定的。他選了那個 Package。他看了 GitHub Issue #247。他知道有 Race Condition 的風險。他還是選了。
但「全權決定」這四個字,從副總嘴裡說出來的時候,聽起來不像是在陳述事實。聽起來像是在畫一條線——一條「你的問題、我的清白」的線。
志明沒有說話。
副總繼續:「目前最重要的是處理客戶退款,還有準備好公關聲明。志明,退款的部分你跟客服主管協調。公關的部分我來處理。」
他站起來,走了。
會議室裡剩下志明、客服主管、公關主管、黃經理。
客服主管跟志明確認了退款流程的細節,然後也走了。公關主管在手機上打聲明稿,一邊打一邊念:「……系統因瞬間流量過大導致部分訂單重複扣款……」
志明站起來,走出會議室。
下午一點,重複扣款的數字停在 37。Fix 有效,沒有新的案例。
但下午兩點,客服主管傳來了一個壞消息:其中一個被重複扣款的客戶,在 Facebook 上發了文。
志明點開那個連結。
那個客戶貼了一張銀行明細的截圖,上面有兩筆相同的扣款紀錄,一筆 2,800 元,間隔不到兩秒。下面寫著:「XX電商你們是在搶錢嗎?打客服等了 20 沒人接!」
留言區已經有兩百多則回覆。有人說「我也被重複扣了」,有人說「這什麼爛系統」,有人說「截圖留證據,告他們」。
志明把連結轉給公關主管。
公關主管回了三個字:「我處理了。」
下午三點,媒體報導了。
不是主流新聞,是一個科技部落格,標題寫著:「XX電商促銷日爆重複扣款災情,消費者哀號」。下面附了那個 Facebook 截圖,還有客服電話被打爆的側面報導。
志明看著那篇報導,把筆電蓋上。
他走到茶水間,發現老張在那裡。
老張正在洗他的保溫杯。他看到志明進來,沒有說話,只是把保溫杯蓋上,轉過身,看了志明一眼。
那個眼神不是「我早就說了」。
那個眼神比「我早就說了」更安靜。像是一個已經把所有話都說完了的人,在等對方自己聽懂。
「老張,」志明說。
老張沒有回應。他拿著保溫杯,走了。
志明站在茶水間,手握著空紙杯,聽著飲水機的壓縮機在牆角嗡嗡地響。
下午五點,副總召集了部門會議。
不是只有他們團隊,是整個工程部門。四十個人擠在大會議室裡,空氣裡有咖啡和汗的氣味。志明坐在中間偏後的位置,不是他想坐那裡,而是他走進會議室的時候,前面的位子已經滿了。
副總站在投影幕前面,雷射筆的紅點在投影片上畫圈。
「……目前重複扣款的客戶已經增加到大約 200 人。」
志明抬頭。
200 人?他中午查的時候是 37 人。
他打開筆電,快速查了一下。
SELECT COUNT(DISTINCT order_id) as affected_orders
FROM payment_logs
WHERE created_at > '2024-06-16 10:00:00'
AND duplicate = true;
結果:203。
他漏算了。他中午只查了 payment_logs 裡面的重複紀錄,但有一些重複扣款是在不同的資料表裡面——金流端的請款紀錄。第三方金流在收到 webhook 的時候,如果兩個 webhook 幾乎同時進來,金流端也會處理兩次請款。這些請款紀錄不在 payment_logs 裡面,而是在金流端的系統裡。
203 筆。
志明把筆電蓋上,手心在出汗。
副總繼續說:「目前我們已經在處理退款,預計今天內完成。公關部門也已經發了聲明。」
他停了一下,然後說:「各位,這個技術選型是志明全權決定的。我當時充分信任他的專業判斷。」
第二次了。
志明坐在會議室裡,聽著同一句話第二次從副總嘴裡說出來。第一次是在小會議室,只有四個人。第二次是在大會議室,四十個人。
四十個人。
他看向大衛。大衛坐在會議桌的另一端,低著頭,手指在筆電鍵盤上沒有動。他的表情不是驚訝,是一種更複雜的東西——像是一直知道這天會來,但真的來了以後,還是不知道該怎麼辦。
他看向老張。老張坐在角落,保溫杯放在桌上,雙手交叉在胸前。他的表情像在說「我早就說了」。不,不是「像在說」。他就是在說。他的整個身體都在說。
他看向小琪。小琪坐在他斜對面,眼眶紅紅的。她沒有哭,但她的嘴唇在微微發抖。她看到志明看她,把頭低下去,假裝在看手機。
志明坐在那裡,感覺到自己正在被什麼東西往下拉。不是重力,是某種更具体的東西——像是會議室的地板在慢慢下沉,而他坐在椅子上,看著其他人的臉一張一張地從他面前經過,每一張臉都帶著一種他無法回應的表情。
大衛的「我不知道該怎麼辦」。
老張的「我早就說了」。
小琪的「我不是故意的」。
副總的「我充分信任他」。
信任。
這個詞怎麼聽起來這麼重。
志明閉上眼睛。
他想起星期一的會議,副總說:「這件事你看著辦,我相信你的判斷。」
他想起自己當時聽到這句話的感覺——不是被信任的喜悅,是一種很微妙的壓力。像是有人把一個東西放到你手上,說「這是你的了」,然後走開。你低頭一看,那個東西是活的,而且你不知道怎麼養它。
他當時應該說什麼?
「副總,這個 Package 有風險,我需要多兩週做壓力測試。」
他沒有說。
他當時怕什麼?怕副總覺得他扛不住。怕團隊覺得他不夠果決。怕自己看起來像個會「要求延期」的主管。
他怕的那些東西,現在都發生了。副總覺得他扛不住。團隊覺得他不夠果決。他看起來不只像個會要求延期的主管,他看起來像個會害公司被媒體報導的主管。
他怕的那些東西,每一件都實現了。只是以一種他沒有預料到的方式。
志明睜開眼睛。
會議已經結束了。人群在往外走。他坐在椅子上,看著他們一個一個經過。有人拍了他的肩膀,沒說話。有人看了他一眼,然後把目光移開。有人什麼都沒做,只是走過去。
小琪經過他身邊的時候,停了一下。
「Zhi哥,」她小聲說,「你還好嗎?」
志明看著她。她的眼眶還是紅的,亮片眼影在會議室的日光燈下閃了一下。
「還好。」他說。
小琪點了一下頭,走了。
大衛經過的時候,停了一下。他沒有看志明,只是站在那裡,嘴唇動了動,像是想說什麼。然後他還是走了,腳步比平常快,肩膀微微聳著,像是在扛一個看不見的東西。志明看著他的背影,想叫住他,但嘴巴沒有開。
老張經過的時候,看了志明一眼。那個眼神停留了大概兩秒。不是「我早就說了」,也不是「你現在才知道」。是一種很安靜的東西。然後他走了,保溫杯在他手裡輕輕晃。
志明一個人坐在空了的會議室裡。
投影幕上還停著副總的最後一張投影片,上面寫著:「Q3 系統優化計畫」。
他坐在那裡,看著那張投影片。
信任。
這個詞在他腦海裡回響。不是因為副總說錯了。是因為副總說對了——技術選型確實是他決定的。Package 是他選的。GitHub Issue 是他看的。lockForUpdate() 是他沒有加的。但「全權決定」這四個字,從副總嘴裡說出來的時候,把所有的上下文都拿掉了。拿掉了「這件事你看著辦」。拿掉了「我相信你的判斷」。拿掉了所有那些「志明你決定就好」的時刻。
那些時刻,現在看起來,不像是信任。
像是埋好的線。
志明站起來,走出會議室。走廊上空空的,日光燈在頭頂嗡嗡地響。
他走到位子,坐下來,打開 Terminal。
[2024-06-16 17:05:43] production.INFO: OrderConfirmationEmailSent {"order_id":14520}
[2024-06-16 17:05:44] production.INFO: OrderConfirmationEmailSent {"order_id":14521}
[2024-06-16 17:05:44] production.INFO: OrderConfirmationEmailSent {"order_id":14521}
他盯著最後兩行。
同一個 order_id。兩筆 OrderConfirmationEmailSent。
Fix 沒有完全生效。
lock 擋住了 webhook 的 Race Condition,但 OrderConfirmationEmail 是另一個 listener 在處理的——那個 listener 沒有 lock。他修了入口,沒有修出口。
他把椅子往後靠,看著天花板。日光燈管上面有一隻飛蛾的屍體,翅膀還是完整的。
Slack 上有一則未讀訊息,是大衛在十分鐘前傳的:
> Zhi哥,不是我跟副總說的。
志明看著這則訊息,手指懸在鍵盤上。他不知道該回什麼。
他關上 Slack,拿出手機,傳了一則訊息給雅婷:
> 今天不回去了。
雅婷秒回:
> 又怎麼了?
那個「又」裡面有很多東西。很多個晚歸的夜晚。很多個取消的週末計畫。很多個「快了」。
他最後回了一個 OK 的手勢,把手機螢幕朝下放在桌上。
窗外,台北的傍晚正在暗下來。志明坐在那裡,看著 log 一行一行地跑,每一行都是 OrderConfirmationEmailSent,每一行都乾乾淨淨,除了偶爾出現的、不應該出現的重複。
他閉上眼睛。
如果那天我做了不同決定。
(第七章 完)