# 🔗 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 整合程式碼!** 🚀