iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
Modern Web

重寫一套比我還老的系統:21 歲的校園文字廣播系統系列 第 5

Day 5|今天的小黃鴨有點高級,會問會答,還不用消耗 token

  • 分享至 

  • xImage
  •  

書接上回,我們從原先的那份 DBML 討論了我們的資料庫設計。
在正式開始今天的內容之前,我用了一些神奇的人工智慧與工人智慧,產出了這份 DBML。

https://ithelp.ithome.com.tw/upload/images/20260919/20182031wLnGx7T2Ta.png

我去老哥偷捲,都自己偷偷動工不揪。

666 稀有鳥類馬德都部鳩出現了,一直部鳩部鳩的叫。
而且這不就要給你派工作了嗎?

所以我要幹啥?

幫我看一下這份 Schema。

啊你不是都畫完了?

對啊。

那我看什麼?

哪裡看不懂就問哪裡,有覺得奇怪的也問。

啊?傳統技藝小黃鴨 Debug 法又要重出江湖了嗎?

可惡被發現了。

今天的小黃鴨會說話

所謂「小黃鴨 Debug 法」,簡單來說就是找一隻小黃鴨,把自己的程式或想法從頭到尾講給它聽。

講著講著,有時候甚至不用等鴨子回答(雖然你應該是等不到它回答啦),你就會突然發現:

「欸幹,我這邊是不是寫錯了?」

至於我們今天的小黃鴨不只會聽,還會問,甚至還會吐槽。

比一般的小黃鴨好用很多,還不用給 API 充錢,就是需要吃點巴拿拿(Banana)犒勞一下

所以我現在是鴨還是猴子?

你是猿神可以了嗎,趕緊開始。

行。

欸老哥,這裡的設計有點猛啊。

https://ithelp.ithome.com.tw/upload/images/20260919/20182031ZTOlXkcOvO.png

這裡的 permissions 是準備要用昨天提到的位運算對吧。

不錯啊,看一眼就懂了,一般人應該會先問為什麼把 permissions 設定成整數。
既然你知道我要幹什麼了,那就由你來跟大家解釋這邊的設計吧。

說好的小黃鴨呢?不應該是你解釋給我聽嗎?

能者過...…咳咳,能者多勞嘛,交給你了。

為什麼 Permission 要設定成 BIGINT?

行,我們來看一下這段:

Table positions {
  id uuid [pk]
  name varchar(255) [not null, unique]
  permissions bigint [not null, default: 0]
  description text
}

以正常的思路來說,我們應該會開一個 Permission 表,然後開一個中介表來記錄哪個 position 有哪些權限對吧。

但蘇某沒有打算這麼做。

假設我們現在有四個權限要記錄:

  • 查看廣播
  • 發布廣播
  • 回覆廣播
  • 處理報修

我們可以把它們塞在同一個整數的不同位元:

查看廣播    0001
發布廣播    0010
回覆廣播    0100
處理報修    1000

這樣如果這個人所擁有的權限是:查看廣播發布廣播

我們只需要對 00010010 做一次 OR 運算得到 0011 也就是 3

之後要檢查某個 Permission,也只需要透過 AND 判斷對應的 bit 有沒有被打開。

位運算在電腦裡本身是非常便宜的,而且這樣一來,一整組 Permission 就可以直接壓在一個 BIGINT 裡,不需要為每一項 Permission 都建立一筆關聯資料。

我補充一下,位運算本身很便宜,不過老實說,我們學校這點使用量根本輪不到 CPU 操心。
真正方便的是,一整組 Permission 可以直接用一個值表示,之後 Role、Position 甚至 Override 都可以用同一套方式做 ORAND

缺點也很明顯,人沒辦法從 3 這個魔法數字直接知道這個人有 查看廣播發布廣播 這兩個權限。

不錯,說得挺好,那你覺得這個可讀性的問題怎麼解決?

開常數定義不就行了。

READ_MESSAGE = 1 << 0
SEND_MESSAGE = 1 << 1
REPLY_MESSAGE = 1 << 2
HANDLE_REPAIR = 1 << 3

對,所以真正寫 Code 的時候,我們不需要直接操作 124 這些神奇數字。
簡單來說,到這裡我們只需要記住一件事:
一個整數,就可以直接表示一整組 Permission。

好,中場休息,喝口水。

我也。欸,剛剛幫你講課,等等下課後珍奶一杯啊。

知道了,我放冰箱,一年後自己來拿。

吔屎丫你!放一年的珍奶是能喝?

等下,老兄我這裡有個問題。

https://ithelp.ithome.com.tw/upload/images/20260919/20182031Ku8Uby6PZw.png

嘿?說。

你這邊的 account 我知道為了共用帳號區分密碼所以設計成 not unique
但這樣要怎麼防止老師的帳號跟學生衝撞到?

利用 position_idaccount 組聯合唯一鍵啊。

那這樣你之後訊息你要怎麼設計推播範圍?如果有老師跟學生的 account 撞名怎麼辦?

Shift。

……等等。

不對!我想到了,我們其實根本不需要拿 account 來決定廣播要送給誰。

啊?不是說要發給班級嗎,不拿帳號怎麼知道哪個是哪班?

Account 真的是「班級」嗎?

就是因為要發給班級,所以才不應該看 Account。
account 是拿來登入的。
我們可以在 group 上新增一個欄位叫 type,用這個欄位去描述這個 group 的屬性。
例如:classgradeofficeroot……
在搜尋時,filter 只要設定 type = class,就能快速找出能設定成目標的 group 了。
再利用共同父節點,對可廣播的組織結構進行還原。

所以你前面硬是多拆一張 groups 出來的目的就是在此?

想多了,我本來只是想要做授權用的,沒想到在這裡派上新的用場了。

欸欸那你那個 parent_id 是不是可以搞成這樣?

高中部
├── 高一
│   ├── 101
│   ├── 102
│   └── 103
├── 高二
│   ├── 201
│   ├── 202
│   └── 203
└── 高三

正確。

在目前的設計中,真正能成為發送目標的只有 type = class 的 Group。

中間的 Group 主要負責描述組織結構,以及讓 UI 可以一次選取一整個範圍。

等一下,那這樣有點香欸。

因為資料庫裡本來就是:

高二
├── 201
├── 202
└── 203

前端顯示出來也是:

高二
├── 201
├── 202
└── 203

你根本不用再另外維護一份「高二有哪些班」?

就說叫建模了,那必須建得像啊 ∠( ᐛ 」∠)_

聽起來很像是剛剛才發現可以這樣用,裝成自己早就想好了。

我不是說了我剛剛才發現可以這樣用嗎 ==。

不過這確實是我很喜歡這個設計的地方。

對使用者而言,它可能完全沒有任何改變。

但對系統而言,可維護性卻提升了:UI 所呈現的組織結構,和資料庫所理解的組織結構,變成了同一件事情。

而且這樣剛才那個 account 撞名的問題其實也消失了。
因為這樣系統根本不關心你的登入帳號叫什麼。
它在廣播推送中只關心:「你到底是哪個 Group?」

對。

看來今天這隻小黃鴨還真的有點用。

賽跑狀況?何意味?

我其實還有想到一個問題,你應該不會想到。

嘿?

你說,如果有兩個能修改組織描述的人,同時修改同一段組織結構,
會發生什麼事呢?

好刁鑽的問題,Race Condition?

對...…

我有兩個思路。

?這麼快?

  1. 想辦法讓整次修改變成一個不可分割的操作
  2. 直接上鎖

有料,但前者很難設計。

?為什麼?

做到那裡再跟你講,這個問題可以在後端解決。

吊我胃口。

對,就吊你們胃口。

不要臉。

(響過一陣敲鑼打鼓聲)

欲知後「逝」如何,且聽下回分解。


上一篇
Day 4|到底誰是 User?把學校的所有人丟進資料庫看看
系列文
重寫一套比我還老的系統:21 歲的校園文字廣播系統5
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言