---
title: "用 Google libphonenumber 套件 處理多國電話號碼驗證（前端 + 後端）"
description: "多國電話號碼的規則繁瑣，手寫 Regex 維護成本高。這篇記錄我們團隊改用 Google 維護的 libphonenumber 生態系的做法：前端用 react-phone-number-input，後端用 libphonenumber-csharp，並統一以 E.164 格式儲存。"
canonical_url: "https://blog.markkulab.net/post/google-libphonenumber-international-phone-validation"
author: "Mark Ku"
author_url: "https://blog.markkulab.net/author/mark-ku"
site: "Mark Ku's Blog"
date_published: "2026-06-07 20:02:25 +0800"
category: "AI"
language: "zh-TW"
license: "CC BY 4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
attribution: "轉載或引用請註明作者並附上原文連結"
---

# 用 Google libphonenumber 套件 處理多國電話號碼驗證（前端 + 後端）

## 前言

做過多國市場產品的話，大概都遇過電話號碼驗證的問題。它看起來簡單，實際上規則不少：各國號碼長度不一、格式各異，有些國家還允許變動長度（例如印尼 10–13 位）。傳統做法是手寫正規表達式（Regex）來檢查，但隨著支援的國家變多，這份 Regex 會越來越難維護。這篇記錄我們團隊改用 Google 維護的 `libphonenumber` 生態系的做法，前後端各用對應的套件。

## 為什麼不自己維護 Regex

### 維護成本不低

各國電話號碼的規則差異不小，以手機為例，日本 11 位、馬來西亞 10–11 位、印尼 10–13 位都合法。把這些規則都寫進 Regex 後，行數會很快累積；之後某個國家一旦調整規則，要在這串 Regex 裡正確改對，並不容易。

### 規則會隨時間變動

各國電信法規的更新比想像中頻繁。光是 2025–2026 年間，英國更新了國家號碼計畫、加拿大實施了千號池（Thousands-Block Pooling）、西班牙也封鎖了偽冒國內號碼的國際來電。這些變更若靠手動維護 Regex，除了耗人力，也容易漏改而導致合法號碼被擋下。

### 容易忽略的細節

一個常見的例子是「去零（Leading Zero）」。很多國家的國內撥號習慣是開頭帶 `0`（例如台灣 `0912-345-678`、日本 `090-1234-5678`），但轉成國際格式後這個 `0` 要去掉（`+886 912345678`、`+81 9012345678`）。自己刻輸入框時，使用者常常不確定「要不要打那個 0」，後台就會收到格式不一致的資料，連帶影響下游系統（簡訊發送、KYC 驗證）。

## Google libphonenumber 套件 生態系

### 核心：libphonenumber

[`libphonenumber`](https://github.com/google/libphonenumber) 是 Google 維護的開源電話號碼處理函式庫，涵蓋全球 218 個國家/地區的號碼規則，撰文時最新版本為 9.0.32（2026-06-05 發布）。Google 有團隊追蹤 ITU 各國號碼計畫變更，大約每兩週更新一次元資料；對使用者來說，只要保持套件版本更新，就不必自己追各國規則的變動。

### 前端：react-phone-number-input

在 React 生態中，[`react-phone-number-input`](https://www.npmjs.com/package/react-phone-number-input)（v3.4.17）是較常見的多國電話輸入元件，每週下載量約 205 萬次。底層使用的 [`libphonenumber-js`](https://github.com/catamphetamine/libphonenumber-js)（v1.13.6）是 Google 原版的純 JavaScript 重寫版，體積約 145 KB（原版約 550 KB），原生支援 TypeScript，對一般商業應用的驗證需求大致夠用。

> 補充：`libphonenumber-js` 不支援緊急號碼、短碼、字母號碼（如 `1-800-GOT-MILK`）及號碼地理編碼。不過這些在一般的會員註冊場景通常用不到。

### 後端：libphonenumber-csharp

[`libphonenumber-csharp`](https://www.nuget.org/packages/libphonenumber-csharp/)（v9.0.32）是 C# 社群對 Google 原版的移植，NuGet 累計下載量超過 9,230 萬次。它透過 GitHub Actions 排程同步 Google 上游更新，延遲通常在 1 天以內，支援 .NET 8.0 及 .NET Standard 2.0。

> 注意：官方提醒，若不保持套件更新，`IsValidNumber` 對較新格式的號碼可能回傳 `false`，導致誤擋合法用戶。

### 架構概覽

前後端各自採用同一生態系的套件，形成兩層驗證：前端負責即時回饋，後端負責最終把關：

```
┌─────────────────────────────────────────────────────┐
│                    使用者瀏覽器                        │
│  ┌───────────────────────────────────────────────┐  │
│  │  react-phone-number-input (v3.4.17)           │  │
│  │  ├─ 國旗選擇 + 國碼自動帶入                      │  │
│  │  ├─ 隨打隨格式化                                │  │
│  │  ├─ 自動去零 (Leading Zero)                    │  │
│  │  └─ 底層：libphonenumber-js (v1.13.6, 145KB)  │  │
│  └───────────────────────────┬───────────────────┘  │
│                              │ E.164 格式            │
│                              │ (+886912345678)       │
└──────────────────────────────┼──────────────────────┘
                               ▼
┌──────────────────────────────┼──────────────────────┐
│                         API Server                   │
│  ┌───────────────────────────┴───────────────────┐  │
│  │  libphonenumber-csharp (v9.0.32)              │  │
│  │  ├─ IsValidNumber() 嚴格驗證                    │  │
│  │  ├─ 解析國碼 + 號碼類型 (手機/市話)               │  │
│  │  └─ 自動同步 Google 上游 (GitHub Actions)       │  │
│  └───────────────────────────┬───────────────────┘  │
│                              │                       │
│                              ▼                       │
│                    資料庫 (E.164 格式儲存)             │
└─────────────────────────────────────────────────────┘
```

## 實作：前端與後端

### 前端實作（React）

`react-phone-number-input` 用起來算直觀，幾行程式碼就能建立一個具備國碼選擇、即時格式化、國旗切換的輸入框：

```tsx
import PhoneInput, { isValidPhoneNumber } from 'react-phone-number-input'
import 'react-phone-number-input/style.css'

function PhoneForm() {
  const [phone, setPhone] = useState<string | undefined>()

  const handleSubmit = () => {
    if (phone && isValidPhoneNumber(phone)) {
      // phone 已經是 E.164 格式，例如 "+886912345678"
      api.post('/register', { phone })
    }
  }

  return (
    <PhoneInput
      international
      defaultCountry="TW"
      countries={['TW', 'JP', 'US', 'MY', 'ID', 'SG', 'TH']}
      value={phone}
      onChange={setPhone}
      placeholder="輸入電話號碼"
    />
  )
}
```

這段程式碼帶來的幾個好處：

- 隨打隨格式化：輸入日本號碼時會自動變成 `090 1234 5678`，比較好讀
- 貼上自動偵測：使用者貼上 `+62 812 3456 7890`，國旗會自動切換成印尼
- 自動去零：選了台灣後輸入 `0912345678`，元件會轉成 `+886 912345678`
- 可限定國家清單：透過 `countries` 參數，只顯示業務有覆蓋的國家

線上 Demo：[https://catamphetamine.github.io/react-phone-number-input/](https://catamphetamine.github.io/react-phone-number-input/)

### 後端驗證（C#）

前端驗證主要是為了使用體驗，真正把關的還是後端。使用 `libphonenumber-csharp` 做驗證：

```csharp
using PhoneNumbers;

public class PhoneValidator
{
    private static readonly PhoneNumberUtil PhoneUtil = PhoneNumberUtil.GetInstance();

    public static bool ValidatePhone(string phoneNumber)
    {
        try
        {
            // 解析號碼（第二個參數為預設國碼區域，傳 null 表示號碼必須包含 "+" 國碼）
            var number = PhoneUtil.Parse(phoneNumber, null);

            // 嚴格驗證：檢查號碼長度、格式是否符合該國規則
            if (!PhoneUtil.IsValidNumber(number))
                return false;

            // 取得號碼類型（手機、市話、VOIP 等）
            var numberType = PhoneUtil.GetNumberType(number);

            // 可依業務需求限制只接受手機號碼
            return numberType == PhoneNumberType.MOBILE
                || numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE;
        }
        catch (NumberParseException)
        {
            return false;
        }
    }
}
```

### 建議：統一用 E.164 格式儲存

不管前端收到什麼格式，存入資料庫前都先轉成 E.164 格式（`+國碼號碼`，例如 `+886912345678`），好處是：

| 面向 | E.164 的優勢 |
|------|-------------|
| 格式統一 | 全球任何號碼都是同一種格式，不會有「帶零/不帶零」的混亂 |
| 下游相容 | Twilio、AWS SNS 等簡訊服務一律要求 E.164 |
| 查詢方便 | 資料庫做唯一性檢查時不用擔心同一號碼有多種寫法 |
| 長度限制 | 最多 15 碼（含國碼），欄位設計很單純 |

## 驗證嚴謹度：只收手機，還是手機市話都收？

前面的驗證只判斷「號碼合不合法」，但實務上常會再多一個需求：**只收手機號，不收市話**。畢竟註冊後要發簡訊驗證碼，市話收不到簡訊。這時就要調整驗證的嚴謹度。

`libphonenumber` 把號碼類型分得很細（手機、市話、免付費、VoIP 等），對應到兩種驗證嚴謹度：

| 嚴謹度 | 手機 | 市話 | 說明 |
|--------|------|------|------|
| `max`（寬鬆） | ✅ | ✅ | 只要格式、長度符合該國規則就通過，不分號碼類型 |
| `mobile`（嚴格） | ✅ | ❌ | 只接受手機號，合法的市話也會被判 `false` |

後端 `libphonenumber-csharp` 一律使用完整 metadata，`IsValidNumber()` 預設就是 `max` 行為（兩種都收）。如果要做到只收手機，前後端都要**額外對齊**，不然會出現「前端讓你送、後端把你擋下來」的落差。

### 前端對齊（這個坑我自己踩過）

整段最容易卡住的就是這一步，我自己也被它繞進去過，所以特別講清楚。

`react-phone-number-input`（還有它底層的 `libphonenumber-js`）為了縮小體積，**預設載入的是 `min` 這份精簡版規則表**。`min` 只保留「號碼長度」的規則，**完全沒有「判斷號碼是手機還是市話」的規則**。

問題出在它**不會報錯**。當你呼叫 `getType()` 想知道號碼類型時，它對大多數國家只會安靜地回傳 `undefined`。程式不會 crash、不會跳警告，所以很容易以為「我有在判斷類型」，實際上那段判斷根本沒生效：

```tsx
// ❌ 踩坑：用預設路徑 import，載入的是 min（沒有類型規則）
import { parsePhoneNumber } from 'react-phone-number-input'

const type = parsePhoneNumber('+886912345678')?.getType()
// type 永遠是 undefined
// 於是「type === 'MOBILE'」永遠成立不了 → 連合法手機都被擋光
// 寫了等於沒寫，而且還不會報錯
```

解法很單純：把 import 路徑改成 `/max` 子套件，就會載入含「類型規則」的完整版，`getType()` 才會真正生效：

```tsx
// ✅ 正確：從 /max import，才有判斷類型的能力
import { parsePhoneNumber } from 'react-phone-number-input/max'

const type = parsePhoneNumber('+886912345678')?.getType()
// type === 'MOBILE'，這下判斷才有效
```

> **一句話記法**：只要你打算用 `getType()` 分辨手機 / 市話，import 路徑就一定要帶 `/max`（或 `/mobile`），千萬別用預設的那個。

知道這點之後，前端的手機驗證就可以這樣寫：

```tsx
import { parsePhoneNumber } from 'react-phone-number-input/max'

function isValidMobile(value?: string): boolean {
  if (!value) return false
  const phone = parsePhoneNumber(value)
  if (!phone || !phone.isValid()) return false

  const type = phone.getType()
  // 接受手機；號段重疊無法判定的國家（FIXED_LINE_OR_MOBILE / undefined）也放行
  return type === 'MOBILE'
    || type === 'FIXED_LINE_OR_MOBILE'
    || type === undefined
}
```

> 如果想要最嚴格的「只收手機」，也可以直接 import `react-phone-number-input/mobile`，它的 `isValidPhoneNumber` 會把市話直接判成 `false`。但這個 bundle 對號段重疊的國家（如美國）也會一律擋掉，使用前要先確認目標國家有獨立的手機號段。

### 後端對齊

後端因為一律使用完整 metadata，類型判斷本來就準。把前面的 `ValidatePhone` 改成限制手機（其實就是文章前面後端範例已經在做的事）：

```csharp
var numberType = PhoneUtil.GetNumberType(number);

// 接受手機；號段重疊無法判定的國家放行（這就是「bypass」）
return numberType == PhoneNumberType.MOBILE
    || numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE;
```

### 號段重疊的國家怎麼辦

有些國家的手機與市話號段是重疊的，最典型的是美國 ， 同一段號碼可能是手機也可能是市話，`libphonenumber` 無法分辨，於是回傳 `FIXED_LINE_OR_MOBILE`。這時若嚴格只收 `MOBILE`，連合法的美國手機都會被擋下來。

`libphonenumber` 對這種情況留了一個「bypass」：把 `FIXED_LINE_OR_MOBILE`（以及前端 `min` metadata 下的 `undefined`）也視為通過，等於在「無法判定」時選擇放行，避免誤殺合法號碼。要不要開這個 bypass，取決於你支援哪些國家：

| 國家 | 類型判斷結果 | 嚴格 mobile 驗證 |
|------|-------------|-----------------|
| 馬來西亞、印尼、中國、泰國、越南 | 有獨立手機號段，回傳 `MOBILE` | ✅ 安全，不會誤判 |
| 美國、加拿大 | 號段重疊，回傳 `FIXED_LINE_OR_MOBILE` | ⚠️ 需要 bypass，否則誤擋 |

我們團隊主要覆蓋東南亞與中國市場，這些國家大多有獨立的手機號段，類型判斷會穩定回傳 `MOBILE`，嚴格驗證不太會誤判。但程式碼仍保留 `FIXED_LINE_OR_MOBILE` 的 bypass，避免日後擴張到美加市場時，才發現合法用戶被擋在門外。

## 太嚴格會掉註冊率：把驗證強度調得「剛剛好」

前面談的都是「要不要分手機/市話」，但實務上還有一個更現實的問題：**驗證太嚴格，會把真實用戶擋在門外，註冊率跟著掉。** 我們自己就是因為註冊轉換率被壓低，才回頭把驗證強度放鬆。

問題的根源在於 `isValidNumber()` 這種「完整驗證」是一把雙面刃。它會比對該國號碼的**完整樣式**——不只是長度，連號段、開頭數字都要符合 metadata 裡登記的規則。一旦電信商新開了一段號碼、而你的套件版本還沒跟上，這些**剛配發出去的合法號碼就會被判 `false`**。使用者覺得「我的號碼明明是對的，為什麼註冊不了」，就直接流失了。

有趣的是，連 `libphonenumber-js` 的作者都在 README 裡明講：他個人偏好較寬鬆的 `isPossible()`，因為「它的強處正是它的弱處」——只驗長度，反而不會因為 metadata 過期而誤殺新號碼。

### 兩種驗證模式：完整驗證 vs 只驗長度

`libphonenumber` 同時提供兩種驗證強度，前後端都有對應 API：

| 模式 | 前端（libphonenumber-js） | 後端（C#） | 檢查內容 | 特性 |
|------|------|------|------|------|
| 完整驗證（嚴格） | `isValid()` | `IsValidNumber()` | 長度 **+** 號段樣式 | 資料最乾淨，但 metadata 過期會誤擋新號碼 |
| 只驗長度（寬鬆） | `isPossible()` | `IsPossibleNumber()` | 只看位數是否可能合法 | 較不會誤殺，對新號段更耐放 |

### 三段式驗證強度

把「分手機/市話」和「完整驗證/只驗長度」兩個軸交叉，實務上常落在三種設定，依「能接受多少髒資料」與「多在意註冊率」來選：

| 強度 | 手機 | 市話 | 適合情境 |
|------|------|------|----------|
| **最嚴格** | 完整驗證 | 完整驗證 | 資料品質優先，能接受偶爾誤擋（如金流、KYC） |
| **手機嚴格 + 市話寬鬆** | 完整驗證 | 只驗長度 | 主要靠手機發簡訊，但市話也想盡量收 |
| **最寬鬆** | 只驗長度 | 只驗長度 | 衝註冊率優先，先收下來再說（資料後續再清洗） |

前端寫法（沿用前面 `/max` 的 import，`getType()` 才有作用）：

```tsx
import { parsePhoneNumber } from 'react-phone-number-input/max'

type Strictness = 'strict' | 'mobileStrict' | 'loose'

function validatePhone(value: string | undefined, level: Strictness): boolean {
  if (!value) return false
  const phone = parsePhoneNumber(value)
  if (!phone) return false

  // 任何模式都先做最基本的長度檢查（位數、國碼是否可能合法）
  if (!phone.isPossible()) return false

  const type = phone.getType()
  const isMobile =
    type === 'MOBILE' || type === 'FIXED_LINE_OR_MOBILE' || type === undefined

  switch (level) {
    case 'strict':       // 手機、市話都要過完整 pattern 驗證
      return phone.isValid()
    case 'mobileStrict': // 手機嚴格、市話只驗長度（已通過 isPossible）
      return isMobile ? phone.isValid() : true
    case 'loose':        // 只驗長度
      return true
  }
}
```

後端 C# 對應寫法：

```csharp
public enum PhoneStrictness { Strict, MobileStrict, Loose }

public static bool ValidatePhone(string phoneNumber, PhoneStrictness level)
{
    try
    {
        var number = PhoneUtil.Parse(phoneNumber, null);

        // 任何模式都先擋掉「長度離譜 / 國碼錯誤」
        if (!PhoneUtil.IsPossibleNumber(number))
            return false;

        var type = PhoneUtil.GetNumberType(number);
        bool isMobile = type == PhoneNumberType.MOBILE
            || type == PhoneNumberType.FIXED_LINE_OR_MOBILE;

        return level switch
        {
            // 最嚴格：手機、市話都要符合完整號碼樣式
            PhoneStrictness.Strict => PhoneUtil.IsValidNumber(number),

            // 手機嚴格、市話寬鬆：手機過完整驗證，市話只要長度合理就收
            PhoneStrictness.MobileStrict => isMobile
                ? PhoneUtil.IsValidNumber(number)
                : true,

            // 最寬鬆：長度可能合法就收（已通過 IsPossibleNumber）
            PhoneStrictness.Loose => true,

            _ => PhoneUtil.IsValidNumber(number),
        };
    }
    catch (NumberParseException)
    {
        return false;
    }
}
```

> 想給更友善的錯誤訊息時，前端可用 `validatePhoneNumberLength()`：長度不對時它會回傳 `TOO_SHORT` / `TOO_LONG`，而不是只給一個 `false`，方便提示「號碼太短」之類的具體原因。

### 寬鬆模式的坑：短碼會被放行（我踩過）

放寬到「只驗長度」是有代價的，這個坑我自己實際踩到過。`isPossible()` / `IsPossibleNumber()` **只看位數落在該國可能的長度區間內**，不比對真實號段。問題是很多國家的**短碼（Short Code）**——例如客服／服務號碼、企業簡碼（台灣的 `165`、`1922`，或各家電信的三～五碼服務號）——位數剛好也落在「可能合法」的區間裡，於是這些根本不是使用者手機的號碼，在最寬鬆模式下會被**判成通過**。

也就是說，「衝註冊率」把驗證放到最鬆之後，資料庫裡可能混進一批短碼或服務號，之後要發簡訊驗證碼時才發現送不出去。所以純 `loose` 模式不能只靠 `isPossible()` 一關把守，至少要再擋掉短碼。

後端 C# 可以用 `ShortNumberInfo` 補一道防線，把短碼直接踢掉：

```csharp
using PhoneNumbers;

var shortInfo = ShortNumberInfo.GetInstance();

// loose 模式收下前，先確認它不是短碼
if (shortInfo.IsValidShortNumber(number))
    return false; // 是短碼／服務號，不當成使用者電話
```

前端因為 `libphonenumber-js` **本來就不支援短碼解析**（前面提過），這些簡碼多半在 `parsePhoneNumber()` 階段就拿不到有效號碼；真正要小心的是**後端**——它載入完整 metadata，`IsPossibleNumber()` 對短碼長度會很寬容。

> **一句話記法**：`isPossible()` 只保證「位數看起來像個電話」，不保證「這是一支能收簡訊的手機」。開最寬鬆模式時，記得對後端補一層 `ShortNumberInfo` 過濾。

### 用 example number 當輸入提示，把錯誤擋在輸入前

降低註冊摩擦還有一招：直接告訴使用者「這個國家的號碼長什麼樣」。`libphonenumber` 內建每個國家的範例號碼，可以拿來當輸入框的 placeholder 或錯誤訊息範例，使用者一看就知道格式，自然少打錯。

前端用 `getExampleNumber()`：

```tsx
import { getExampleNumber } from 'libphonenumber-js'
import examples from 'libphonenumber-js/mobile/examples'

const example = getExampleNumber('TW', examples)
example?.formatNational()      // '0912 345 678'
example?.formatInternational() // '+886 912 345 678'
```

拿來當 placeholder，使用者選哪個國家就提示那個國家的範例：

```tsx
<PhoneInput
  international
  defaultCountry="TW"
  value={phone}
  onChange={setPhone}
  placeholder={getExampleNumber('TW', examples)?.formatNational()}
/>
```

後端 C# 也有對應 API，適合放進 API 文件或驗證失敗的回應訊息裡：

```csharp
var example = PhoneUtil.GetExampleNumberForType("TW", PhoneNumberType.MOBILE);

// 國內格式：0912 345 678
var national = PhoneUtil.Format(example, PhoneNumberFormat.NATIONAL);

// 國際格式：+886 912 345 678
var international = PhoneUtil.Format(example, PhoneNumberFormat.INTERNATIONAL);
```

這樣一來，驗證失敗時不只回「號碼格式錯誤」，還能附上「正確格式範例：0912 345 678」，使用者改起來更直覺，註冊流程也更順。

## 商業考量：可以免費商用嗎？

| 套件 | 授權 | 商用 | 閉源 | 傳染性 |
|------|------|------|------|--------|
| `react-phone-number-input` | MIT | ✅ 完全允許 | ✅ 不需公開原始碼 | ❌ 無 |
| `libphonenumber-js` | MIT | ✅ 完全允許 | ✅ 不需公開原始碼 | ❌ 無 |
| `libphonenumber-csharp` | Apache 2.0 | ✅ 完全允許 | ✅ 不需公開原始碼 | ❌ 無 |

簡單說，不管是 MIT 還是 Apache 2.0，對商業與閉源專案都算友善。可以自由使用、修改、分發，也能包裝在付費軟體中，不會有像 GPL 那種「強迫開源」的傳染性問題，導入正式產品時不太需要擔心授權。

## 結論

整理一下採用 `libphonenumber` 生態系後的幾個好處：

- 維護成本降低：Google 大約每兩週更新各國規則，平常只需要保持套件版本更新（`npm update` / `dotnet update`）
- 資料品質較穩定：前端即時格式化 + 後端驗證，減少格式錯亂的資料進到資料庫
- 使用體驗較好：國旗選擇、隨打隨格式化、貼上自動偵測，註冊表單操作起來比較順
- 驗證強度可調：從「完整驗證」到「只驗長度」（`isValid` / `isPossible`），可以在「資料品質」與「註冊轉換率」之間找平衡，不必一刀切；再搭配 example number 當輸入提示，進一步降低填錯率

如果專案裡還留著一大段各國電話的 Regex，可以考慮換成這套做法，把這部分維護交給上游處理。

## 參考資料

- [Google libphonenumber 套件 - GitHub Releases](https://github.com/google/libphonenumber/releases)
- [react-phone-number-input - npm](https://www.npmjs.com/package/react-phone-number-input)
- [libphonenumber-js - GitHub](https://github.com/catamphetamine/libphonenumber-js)
- [isValid vs isPossible 取捨說明 - libphonenumber-js README](https://gitlab.com/catamphetamine/libphonenumber-js#using-phone-number-validation-feature)
- [libphonenumber-csharp - NuGet](https://www.nuget.org/packages/libphonenumber-csharp/)
- [libphonenumber-csharp - GitHub](https://github.com/twcclegg/libphonenumber-csharp)
- [E.164 Phone Format Guide - Sent.dm](https://www.sent.dm/en/resources/sms-pricing/e164-phone-format)
- [Phone Number Validation Best Practices - Twilio](https://www.twilio.com/en-us/blog/best-practices-phone-number-validation-user-enrollment)

---

## 關於本文與作者

本文出自 [Mark Ku's Blog](https://blog.markkulab.net/post/google-libphonenumber-international-phone-validation)

授權條款： [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) — 轉載或引用請註明作者並附上原文連結

### 關於作者

**[Mark Ku](https://blog.markkulab.net/author/mark-ku)** — Software Solution Provider

- 10+ 年資深軟體工程師，現為 AI 應用 Builder
- 專注大型平台架構設計，從北美電商到AI SaaS訂閱收費系統
- 結合 AI Agent 與自動化，打造高效可演進的產品技術基礎

### 作者開發的免費工具

以下工具皆可免費使用：

- [免費 PDF 簽名工具](https://blog.markkulab.net/tools/pdf-sign): 線上 PDF 簽名工具，瀏覽器內完成手繪、打字、上傳簽名，可拖曳放置、縮放、下載。所有處理都在你的裝置完成，檔案不會上傳。
- [VS Code Refactory](https://blog.markkulab.net/tools/refactory): Refactory 是一款 VS Code 重構擴充套件：34 個重構動作、37 條 code smell 檢查、Code Health 儀表板、18 種語言、534 支測試。懂你的專案慣例：介面放哪、DI 註冊寫在哪、'use client' 該不該加；還會用 git 修改頻率 × 複雜度排出「該先修哪個檔案」，並一鍵把壞味道交給你自己電腦上的 Claude Code 修。免費使用，原始碼不離開你的機器。
- [DB-Kit 資料庫管理工具](https://blog.markkulab.net/tools/db-kit): DB-Kit 是一個用 Tauri + Rust + React 打造的輕量跨平台資料庫管理工具，用單一一致的介面同時管理 MySQL、MariaDB、PostgreSQL、SQL Server、Oracle、SQLite、MongoDB、Redis、Kafka、Elasticsearch 與 RabbitMQ 十一種資料來源：連線密碼以 OS keychain 加密、SSH Tunnel、完整 CRUD、視覺化查詢建構器、多結果集同時顯示、跨連線資料傳輸與比對同步、Excel / CSV 匯入匯出、執行計畫視覺化、ER 圖、排程備份、SQL 壓力測試（p50～p99 延遲百分位）、15 條規則的 SQL 審查、Kafka 訊息瀏覽與監控告警；繁中 / 英文雙語介面，內建 AI 助手（自然語言生成 SQL、AI 審查與調校建議）與命令列工具 dbk。免費開源（MIT），提供 Windows / macOS / Linux 安裝檔。
- [VS Code Super Mermaid](https://blog.markkulab.net/tools/super-mermaid): Super Mermaid 是一款 VS Code 擴充套件：開箱即用的漂亮 Mermaid 圖表，自動上色、即時預覽、滑鼠平移縮放、PNG / SVG 高解析匯出，內建 21 種範本與多種主題。免費開源（MIT）。
- [React Super Mermaid](https://blog.markkulab.net/tools/react-super-mermaid): react-super-mermaid 是一個開源 React 元件庫：一行 <MermaidViewer> 即可渲染漂亮的 Mermaid 圖表，內建 colorful / sketch 主題、平移縮放、圖內搜尋、SVG / PNG 高解析匯出。輕量、SSR 安全、完整 TypeScript 型別。免費開源（MIT）。
- [Jira / Confluence Super Mermaid](https://blog.markkulab.net/tools/jira-super-mermaid): Atlassian Forge app：在 Jira issue 與 Confluence 內文直接寫 Mermaid 語法，畫流程圖、時序圖、狀態機與甘特圖。11 種圖表、SVG / PNG 匯出、明暗主題、完整中日韓文字支援。取得 Runs on Atlassian 資格：圖表存在你自己的站台，app 不呼叫任何第三方服務。免費，即將上架 Atlassian Marketplace。
- [Mermaid 線上預覽](https://blog.markkulab.net/tools/mermaid-preview): 在瀏覽器裡寫 Mermaid、即時看圖，整張圖表壓進網址就能分享。免註冊、不上傳伺服器，相容 mermaid.live 的分享連結。
- [React Intl Phone Number](https://blog.markkulab.net/tools/react-intl-phone-number): react-intl-phone-number 是一個開源 React 元件：framework-agnostic、不依賴 antd，提供 E.164 進出、可搜尋國旗 / 國碼下拉、可配置驗證等級（strict / mobile-strict / loose）、可主題化 CSS 與 i18n，電話邏輯由 google-libphonenumber 驅動。輕量、完整 TypeScript 型別。免費開源（MIT）。
- [Uptime Kuma Cluster](https://blog.markkulab.net/tools/uptime-kuma-cluster): 把單機版 Uptime Kuma 改造成高可用叢集：OpenResty + Lua 智慧負載平衡、MariaDB 共享狀態、健康檢查與自動 Failover，附叢集管理 REST API，一行 Docker Compose 啟動。免費開源（MIT）。
- [特教專案](https://blog.markkulab.net/education): 為特殊教育學生製作的學習教材

### 每日 Podcast

- [Mark's Tech Insights 每日 AI 新鮮事](https://blog.markkulab.net/category/tech-news): 每日精選 AI 與科技趨勢，透過語音摘要快速掌握最新技術動態，涵蓋 AI 應用、軟體架構、DevOps 與工程實戰。 — RSS: https://blog.markkulab.net/feed.xml
- [AI股市蝦聊](https://blog.markkulab.net/category/ai-stock-chat): 每個交易日用 AI 分析台股盤勢，以雙人對話聊當天的盤中觀察與隔日預測。 — RSS: https://blog.markkulab.net/ai-stock-chat/feed.xml
- [開源好物週報](https://blog.markkulab.net/category/open-source-weekly): 每週從 Hacker News、GitHub、Reddit 聲量雷達精選免費開源工具，以雙人對話聊解決什麼痛點與怎麼快速上手。 — RSS: https://blog.markkulab.net/open-source-weekly/feed.xml

### 電子報

[訂閱電子報](https://blog.markkulab.net/subscribe) — 第一時間收到新文章通知，無垃圾信、隨時可取消訂閱。
