# 🔗 Google Workspace 整合指南 ## 📋 概述 整合 Google Calendar 和 Google Meet 到 HR Portal,讓員工可以: - 📅 雙向同步 Google Calendar - 🎥 透過您的 Google 帳號建立 Google Meet 會議 - 📧 自動發送會議邀請 - 🔔 接收 Google 的會議提醒 --- ## 🎯 整合方案 ### 方案 A: Google Workspace API (推薦) ⭐ **使用您的 Google One 帳號作為服務帳號** **優點**: - ✅ 直接使用您現有的 Google 帳號 - ✅ 完整的 Google Meet 功能 - ✅ 無需額外付費 - ✅ Google Calendar 原生體驗 **需要的 API**: 1. **Google Calendar API** - 行事曆同步 2. **Google Meet API** - 建立會議 3. **Google People API** - 聯絡人管理 (可選) --- ## 🔧 設定步驟 ### 步驟 1: 建立 Google Cloud Project 1. 訪問 [Google Cloud Console](https://console.cloud.google.com/) 2. 建立新專案 ``` 專案名稱: HR Portal Integration 組織: (您的組織或個人) ``` 3. 啟用 API - Google Calendar API - Google Meet API (部分 Google Workspace Admin API) - Google People API ### 步驟 2: 建立 OAuth 2.0 憑證 1. **API 和服務 → 憑證** 2. **建立憑證 → OAuth 用戶端 ID** ``` 應用程式類型: 網頁應用程式 名稱: HR Portal 已授權的 JavaScript 來源: - https://hr.porscheworld.tw - http://localhost:3000 (開發用) 已授權的重新導向 URI: - https://hr.porscheworld.tw/api/v1/auth/google/callback - http://localhost:3000/auth/google/callback (開發用) ``` 3. **下載 JSON 憑證檔案** - 檔名: `google-credentials.json` - 儲存到: `backend/secrets/` 4. **記錄以下資訊**: ``` Client ID: xxxxx.apps.googleusercontent.com Client Secret: xxxxxxxxxxxxxx ``` ### 步驟 3: 設定 OAuth 同意畫面 1. **OAuth 同意畫面** ``` 使用者類型: 外部 (或內部,如果有 Google Workspace) 應用程式資訊: - 應用程式名稱: HR Portal - 使用者支援電子郵件: admin@porscheworld.tw - 應用程式標誌: (您的 Logo) 授權網域: - porscheworld.tw 開發人員聯絡資訊: - it@porscheworld.tw ``` 2. **範圍 (Scopes)** ``` 新增以下範圍: - https://www.googleapis.com/auth/calendar - https://www.googleapis.com/auth/calendar.events - https://www.googleapis.com/auth/meetings.space.created - https://www.googleapis.com/auth/userinfo.email - https://www.googleapis.com/auth/userinfo.profile ``` 3. **測試使用者** (如果應用程式處於測試階段) - 新增公司員工的 Gmail 地址 --- ## 💻 後端實作 ### 1. 安裝相關套件 ```bash cd backend pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client ``` 更新 `requirements.txt`: ```txt google-auth==2.26.2 google-auth-oauthlib==1.2.0 google-auth-httplib2==0.2.0 google-api-python-client==2.114.0 ``` ### 2. 環境變數設定 編輯 `backend/.env`: ```env # Google OAuth GOOGLE_CLIENT_ID=xxxxx.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=xxxxxxxxxxxxxx GOOGLE_REDIRECT_URI=https://hr.porscheworld.tw/api/v1/auth/google/callback # Google API Scopes GOOGLE_SCOPES=https://www.googleapis.com/auth/calendar,https://www.googleapis.com/auth/calendar.events # 服務帳號 (您的 Google One 帳號) GOOGLE_SERVICE_ACCOUNT_EMAIL=porsche.chen@gmail.com ``` ### 3. Google 服務類別 創建 `backend/app/services/google_service.py`: ```python """ Google Workspace 整合服務 """ from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import Flow from googleapiclient.discovery import build from datetime import datetime, timedelta from typing import List, Dict, Optional import os from app.core.config import settings class GoogleService: """Google API 服務""" def __init__(self, credentials: Credentials): self.credentials = credentials self.calendar_service = build('calendar', 'v3', credentials=credentials) @staticmethod def get_auth_url(state: str) -> str: """ 取得 Google OAuth 授權 URL Args: state: 狀態參數 (用於防止 CSRF) Returns: 授權 URL """ flow = Flow.from_client_config( { "web": { "client_id": settings.GOOGLE_CLIENT_ID, "client_secret": settings.GOOGLE_CLIENT_SECRET, "redirect_uris": [settings.GOOGLE_REDIRECT_URI], "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", } }, scopes=settings.GOOGLE_SCOPES.split(',') ) flow.redirect_uri = settings.GOOGLE_REDIRECT_URI authorization_url, _ = flow.authorization_url( access_type='offline', include_granted_scopes='true', state=state, prompt='consent' ) return authorization_url @staticmethod def get_credentials_from_code(code: str) -> Credentials: """ 用授權碼換取憑證 Args: code: OAuth 授權碼 Returns: Google Credentials """ flow = Flow.from_client_config( { "web": { "client_id": settings.GOOGLE_CLIENT_ID, "client_secret": settings.GOOGLE_CLIENT_SECRET, "redirect_uris": [settings.GOOGLE_REDIRECT_URI], "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", } }, scopes=settings.GOOGLE_SCOPES.split(',') ) flow.redirect_uri = settings.GOOGLE_REDIRECT_URI flow.fetch_token(code=code) return flow.credentials # === Google Calendar Methods === def list_events( self, time_min: Optional[datetime] = None, time_max: Optional[datetime] = None, max_results: int = 100 ) -> List[Dict]: """ 列出 Google Calendar 事件 Args: time_min: 開始時間 time_max: 結束時間 max_results: 最大結果數 Returns: 事件列表 """ if not time_min: time_min = datetime.utcnow() if not time_max: time_max = time_min + timedelta(days=30) events_result = self.calendar_service.events().list( calendarId='primary', timeMin=time_min.isoformat() + 'Z', timeMax=time_max.isoformat() + 'Z', maxResults=max_results, singleEvents=True, orderBy='startTime' ).execute() return events_result.get('items', []) def create_event( self, summary: str, start_time: datetime, end_time: datetime, description: Optional[str] = None, location: Optional[str] = None, attendees: Optional[List[str]] = None, add_meet: bool = True ) -> Dict: """ 建立 Google Calendar 事件 Args: summary: 事件標題 start_time: 開始時間 end_time: 結束時間 description: 事件描述 location: 地點 attendees: 參與者 email 列表 add_meet: 是否新增 Google Meet 連結 Returns: 建立的事件資料 """ event = { 'summary': summary, 'start': { 'dateTime': start_time.isoformat(), 'timeZone': 'Asia/Taipei', }, 'end': { 'dateTime': end_time.isoformat(), 'timeZone': 'Asia/Taipei', }, } if description: event['description'] = description if location: event['location'] = location if attendees: event['attendees'] = [{'email': email} for email in attendees] # 新增 Google Meet if add_meet: event['conferenceData'] = { 'createRequest': { 'requestId': f"meet-{datetime.utcnow().timestamp()}", 'conferenceSolutionKey': {'type': 'hangoutsMeet'} } } # 建立事件 created_event = self.calendar_service.events().insert( calendarId='primary', body=event, conferenceDataVersion=1 if add_meet else 0, sendUpdates='all' # 發送邀請給所有參與者 ).execute() return created_event def update_event( self, event_id: str, summary: Optional[str] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, description: Optional[str] = None ) -> Dict: """ 更新 Google Calendar 事件 Args: event_id: Google Event ID summary: 新標題 start_time: 新開始時間 end_time: 新結束時間 description: 新描述 Returns: 更新後的事件資料 """ # 取得現有事件 event = self.calendar_service.events().get( calendarId='primary', eventId=event_id ).execute() # 更新欄位 if summary: event['summary'] = summary if start_time: event['start']['dateTime'] = start_time.isoformat() if end_time: event['end']['dateTime'] = end_time.isoformat() if description: event['description'] = description # 更新事件 updated_event = self.calendar_service.events().update( calendarId='primary', eventId=event_id, body=event, sendUpdates='all' ).execute() return updated_event def delete_event(self, event_id: str): """ 刪除 Google Calendar 事件 Args: event_id: Google Event ID """ self.calendar_service.events().delete( calendarId='primary', eventId=event_id, sendUpdates='all' ).execute() def get_meet_link(self, event_id: str) -> Optional[str]: """ 取得 Google Meet 連結 Args: event_id: Google Event ID Returns: Meet 連結或 None """ event = self.calendar_service.events().get( calendarId='primary', eventId=event_id ).execute() conference_data = event.get('conferenceData', {}) entry_points = conference_data.get('entryPoints', []) for entry in entry_points: if entry.get('entryPointType') == 'video': return entry.get('uri') return None ``` --- ## 🔐 認證流程 ### 1. 員工首次連接 Google 帳號 ``` 員工點擊「連接 Google Calendar」 ↓ 重定向到 Google 授權頁面 ↓ 員工登入並授權 ↓ Google 重定向回 HR Portal (帶授權碼) ↓ 後端用授權碼換取 Access Token ↓ 儲存 Token 到資料庫 (加密) ↓ 開始同步 Google Calendar ``` ### 2. 建立會議流程 ``` 員工在 HR Portal 建立會議 ↓ 選擇「使用 Google Meet」 ↓ 後端呼叫 Google Calendar API ↓ 自動建立 Calendar 事件 + Google Meet 連結 ↓ 發送邀請給參與者 ↓ 返回 Meet 連結給前端 ↓ 員工可直接點擊加入會議 ``` --- ## 📦 資料庫擴充 新增欄位到 `employees` 表: ```sql ALTER TABLE employees ADD COLUMN IF NOT EXISTS google_calendar_connected BOOLEAN DEFAULT FALSE, google_access_token TEXT, -- 加密儲存 google_refresh_token TEXT, -- 加密儲存 google_token_expiry TIMESTAMP, google_calendar_id VARCHAR(255), last_google_sync TIMESTAMP; ``` --- ## 🎨 前端整合 ### React 範例 - 連接 Google Calendar ```typescript // 連接 Google Calendar 按鈕 const ConnectGoogleButton = () => { const handleConnect = async () => { const response = await fetch('/api/v1/auth/google/authorize'); const { auth_url } = await response.json(); // 開啟授權視窗 window.location.href = auth_url; }; return ( ); }; ``` ### React 範例 - 建立 Google Meet 會議 ```typescript const CreateMeetingButton = () => { const createMeeting = async () => { const meeting = { title: "團隊會議", start_time: "2026-02-08T14:00:00+08:00", end_time: "2026-02-08T15:00:00+08:00", attendees: ["alice@ease.taipei", "bob@lab.taipei"], use_google_meet: true }; const response = await fetch('/api/v1/calendar/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(meeting) }); const event = await response.json(); // 顯示 Google Meet 連結 alert(`會議已建立!\nGoogle Meet: ${event.hangout_link}`); }; return ; }; ``` --- ## 🔄 同步機制 ### 雙向同步策略 1. **從 Google Calendar 同步到 HR Portal** (每 15 分鐘) - 使用 Google Calendar API 的 sync token - 只同步變更的事件 - 更新本地資料庫 2. **從 HR Portal 同步到 Google Calendar** (即時) - 員工在 HR Portal 建立事件 - 立即推送到 Google Calendar - 取得 Google Event ID 並儲存 3. **衝突處理** - Google 為主要來源 - 顯示衝突警告 - 讓員工選擇保留哪個版本 --- ## ✨ 推薦的 UI/UX 套件 ### 1. FullCalendar (推薦) ⭐ **網址**: https://fullcalendar.io/ **特色**: - ✅ 功能完整的行事曆元件 - ✅ 支援日/週/月/議程視圖 - ✅ 拖放事件 - ✅ 與 Google Calendar 整合良好 - ✅ React 版本: `@fullcalendar/react` **安裝**: ```bash npm install @fullcalendar/react @fullcalendar/daygrid @fullcalendar/timegrid @fullcalendar/interaction @fullcalendar/google-calendar ``` **使用範例**: ```typescript import FullCalendar from '@fullcalendar/react'; import dayGridPlugin from '@fullcalendar/daygrid'; import timeGridPlugin from '@fullcalendar/timegrid'; import interactionPlugin from '@fullcalendar/interaction'; import googleCalendarPlugin from '@fullcalendar/google-calendar'; function Calendar() { return ( ); } ``` ### 2. React Big Calendar **網址**: https://jquense.github.io/react-big-calendar/ **特色**: - ✅ 輕量級 - ✅ 類似 Google Calendar 介面 - ✅ 自訂性高 ### 3. Ant Design Calendar 如果您使用 Ant Design: ```typescript import { Calendar, Badge } from 'antd'; ``` --- ## 📊 功能對比 | 功能 | Jitsi Meet (自架) | Google Meet (整合) | |------|------------------|-------------------| | 費用 | 免費 | 包含在 Google One | | 時間限制 | 無 | 24小時 | | 參與人數 | 視伺服器資源 | 100人 (Google One) | | 錄影 | 支援 | 支援 | | 螢幕分享 | 支援 | 支援 | | 背景模糊 | 支援 | 支援 | | 整合難度 | 中 | 低 | | 維護成本 | 需自行維護 | Google 維護 | **建議**: 使用 Google Meet 整合,更簡單且功能完整! ⭐ --- **下一步我會建立完整的 Google 整合程式碼!** 🚀