跳到主要內容
載入中...

台灣補助通開發日記

1. 事前準備

準備材料

服務用途費用
GitHub存放程式碼免費
自己的 Linux 伺服器部署網站月付
Cloudflare網域 + HTTPS 隧道免費
LINE DevelopersLINE Bot免費
PayPal Developer金流免費(正式收款需商業帳號)
AI模型AI 配對 + 計畫書生成充值制
Google Search ConsoleSEO免費

軟體

# Linux (Ubuntu/Debian)
sudo apt update && sudo apt install -y curl git nginx postgresql postgresql-client
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node -v   # 確認 >= 18
npm -v    # 確認 >= 9

2. 環境建置

安裝 PostgreSQL

sudo systemctl start postgresql
sudo systemctl enable postgresql

# 建立資料庫與使用者
sudo -u postgres psql
CREATE USER subsidy_user WITH PASSWORD '你的密碼';
CREATE DATABASE subsidy_hub OWNER subsidy_user;

初始化 Next.js 專案

mkdir subsidy-hub && cd subsidy-hub
npx create-next-app@latest . --typescript --tailwind --eslint --app --src-dir
npm install prisma @prisma/client @prisma/adapter-pg
npm install bcryptjs cheerio iconv-lite lucide-react nodemailer react-markdown remark-gfm xml2js zustand

環境變數(.env)

DATABASE_URL="postgresql://subsidy_user:***@127.0.0.1:5432/subsidy_hub?schema=public"
SITE_URL=https://你的網域

困難 1:DATABASE_URL 密碼中有特殊符號會噴錯

解法:用 URL encoding 取代特殊字元,例如 @ 寫成 %40,# 寫成 %23


3. 專案初始化

Prisma Schema — 8 個資料表

建立 prisma/schema.prisma,定義以下資料表:

資料表用途
Subsidy補助資料主表(25+ 欄位)
User會員(Email、OAuth、點數、等級)
PlanAI 生成的計畫書
Transaction金流交易紀錄
SubsidyMatchAI 配對結果
UserFavorite使用者收藏
UserSession登入 session
ApplicationStatus補助申請進度
npx prisma migrate dev --name init
npx prisma generate

4. 資料庫設計

最難的決定:補助資料怎麼來?

方式優點缺點
手動輸入精準累死
政府 Open Data API官方資料格式不統一
網頁爬蟲資料量大容易被封鎖
AI 生成快速可能幻覺

我們選的方案:網頁爬蟲為主 + AI 補缺欄位

AI 補缺欄位

爬蟲只能拿到基本資料,像 age_limittargetdocuments 這些欄位常常是空的。用推論補齊後:age_limit 從 5% 提升到 60%,target 和 documents 從 50% 提升到 100%。


5. 核心功能開發

專案結構

src/
  app/
    page.tsx              # 首頁
    subsidies/            # 補助列表 + 詳情
    ai-match/             # AI 配對
    ai-assistant/         # 文件助手(計畫書生成)
    login/register/       # 會員
    admin/                # 管理後台
    api/                  # API 路由
    layout.tsx            # 全站佈局
    sitemap.ts            # SEO
  components/             # 共用元件
  lib/
    db.ts                 # Prisma 客戶端
    session.ts            # 登入 session
    ai/client.ts          # AI 多 provider 支援
    utils.ts              # 工具函式
  prisma/schema.prisma    # 資料庫定義

會員系統(Email + Google OAuth)

使用 Token-based session:crypto.randomUUID() 產生 token,存在資料庫,HttpOnly Cookie 存前端。Google OAuth 透過 NextAuth 或手動授權流程完成。

AI 配對

不採用傳統關鍵字比對,而是讓AI分析使用者的年齡、地區、類別等條件,計算每個補助的匹配分數,回傳最相關的 10 筆。困難 2:Google 封鎖爬蟲

Google 搜尋爬蟲遇到 HTTP 429/403 封鎖。解法:改用 Bing 搜尋(www.bing.com/search),Bing 比較寬鬆。最終用 44 組關鍵字掃全台公部門網站。


6. 金流串接(PayPal)

先去 PayPal Developer Dashboard 建立應用程式,取得 Client ID 和 Secret。

最重要的一步: 把 https://你的網域 加到 PayPal 後台的 Return URLs,否則會跳 401。困難 3:PayPal 沙箱 vs 正式環境

一開始用沙箱測試,但正式上線忘了改回正式 API 網址。解法:用環境變數 PAYPAL_SANDBOX=false 控制,寫在 .env 裡一目瞭然。


7. LINE Bot 串接

申請 LINE Channel

  1. 去 LINE Developers Console → 新增 Provider → 新增 Messaging API Channel
  2. 取得:Channel Secret、Channel Access Token、Your User ID

Webhook 關鍵流程

1. request.text() 讀取 body
2. HMAC-SHA256 驗證簽章
3. JSON.parse() 取代 request.json()
4. 處理訊息(熱門/關鍵字/預設)
5. LINE API 回覆訊息
6. 一律回傳 200

困難 4:request.text() 跟 request.json() 的衝突

程式碼先呼叫 request.text() 做簽章驗證,但 request 的 body stream 只能讀一次。之後的 request.json() 會因為 stream 被消耗而失敗,回傳 400 Invalid JSON。

解法: 只讀一次 request.text(),然後用 JSON.parse() 取代 request.json()困難 5:公司網路封鎖 IPv6

環境沒有公網 IP,IPv6 被封住,導致連不上 LINE 的 API 伺服器。

解法: 在 HTTP 連線強制使用 IPv4:httpx.AsyncHTTPTransport(local_address="0.0.0.0")


8. AI 功能串接

5 套提示詞系統(依補助類型自動切換)

類型AI 角色章節數
創業 / 研發商業計畫書專家9 章
住宅補貼居住補助顧問5 章
育兒津貼育兒福利專家6 章
社福 / 長照社福補助顧問6 章
職訓 / 進修職訓教育專家6 章

表單也要對應

第一次犯的錯: 所有表單共用同一個 state 物件,結果 form.companyName 在商業表單是「公司名稱」,在住宅表單卻是「每月租金」。

解法: 每種表單類型建立專屬 payload(buildPayload()),只送相關欄位給後端。

後端收到後依 formType 解析對應欄位,組不同的 userDataBlock 餵給 AI。


9. 讓網站上線(Cloudflare Tunnel)

為什麼選 Cloudflare Tunnel?

  • 沒有公網 IP → 無法直接暴露 port
  • Cloudflare Tunnel 建立加密通道,不用開防火牆
  • 順便 CDN + DDoS 保護 + HTTPS 自動
# 安裝 cloudflared
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 \
  -o /usr/local/bin/cloudflared
chmod +x /usr/local/bin/cloudflared

# 登入 + 建立隧道
cloudflared tunnel login
cloudflared tunnel create subsidy-hub
cloudflared tunnel route dns subsidy-hub 你的網域

# 設定檔 ~/.cloudflared/subsidy-hub.yml
tunnel: subsidy-hub
credentials-file: /home/你/.cloudflared/隧道ID.json
ingress:
  - hostname: 你的網域
    service: http://localhost:3000
  - service: http_status:404

# 啟動
cloudflared tunnel run subsidy-hub

困難 6:Cloudflare 的 QUIC 被封

部分公司網路封鎖 UDP(QUIC 協定),導致 Tunnel 連不上。

解法: 強制使用 HTTP2:cloudflared tunnel --protocol http2 run subsidy-hub


10. 自動爬蟲與排程

使用 Hermes Cron Job

排程時間用途
補助自動爬蟲每日 08:00搜尋 44 組關鍵字,爬取最新補助
補助截止提醒每日 09:00檢查 7 天內截止的補助,推送到 Telegram

11.遇到的困難與解法

#問題症狀解法
1DATABASE_URL 特殊字元Prisma 連不上URL encoding(@ → %40)
2Google 封鎖爬蟲HTTP 429/403改用 Bing + 44 組關鍵字
3PayPal 沙箱忘了改正式環境無法收款用環境變數 PAYPAL_SANDBOX 控制
4request.stream 只能讀一次Webhook 回傳 400request.text() + JSON.parse()
5公司網路封鎖 IPv6HTTP 連線逾時強制 local_address=’0.0.0.0’(IPv4)
6Cloudflare QUIC 被封Tunnel 連不上–protocol http2
7表單共用 statecompanyName = 公司名或房租?依 formType 建專屬 payload
8扣點在 AI 之前AI 失敗也扣 200 點先生成成功再扣點
9提示詞共用住宅補助也生出 9 章商業計畫書5 套提示詞依類型切換
10title 雙重後綴「補助名 | 台灣補助通 | 台灣補助通」layout 用 template,子頁只放 title

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *