Backend: - schedule_tenant: NC 新容器自動 pgsql 安裝 (_nc_db_check 全新容器處理) - schedule_tenant: NC 初始化加入 Redis + APCu memcache 設定 (修正 OIDC invalid_state) - schedule_tenant: 新租戶 KC realm 自動設定 accessCodeLifespan=600s (修正 authentication_expired) - schedule_account: NC Mail 帳號自動設定 (nc_mail_result/nc_mail_done_at) - schedule_account: NC 台灣國定假日行事曆自動訂閱 (CalDAV MKCALENDAR) - nextcloud_client: 新增 subscribe_calendar() CalDAV 訂閱方法 - settings: 新增系統設定 API (site_title/version/timezone/SSO/Keycloak) - models/result: 新增 nc_mail_result, nc_mail_done_at 欄位 - alembic: 遷移 002(system_settings) 003(keycloak_admin) 004(nc_mail_result) Frontend (Admin Portal): - 新增完整管理後台 (index/tenants/accounts/servers/schedules/logs/settings/system-status) - api.js: Keycloak JS Adapter SSO 整合 (PKCE/S256, fallback KC JS 來源, 自動 token 更新) - index.html: Promise.allSettled 取代 Promise.all,防止單一 API 失敗影響整頁 - 所有頁面加入 try/catch + toast 錯誤處理 - 新增品牌 LOGO 與 favicon Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
374 lines
8.0 KiB
Markdown
374 lines
8.0 KiB
Markdown
# Virtual MIS - 開發規範
|
||
|
||
**版本**: v1.0
|
||
**日期**: 2026-02-27
|
||
|
||
---
|
||
|
||
## 專案結構
|
||
|
||
```
|
||
virtual-mis/
|
||
├── backend/ # 後端服務
|
||
│ ├── app/
|
||
│ │ ├── api/ # API 路由
|
||
│ │ │ ├── v1/
|
||
│ │ │ │ ├── tenant_onboarding.py
|
||
│ │ │ │ ├── service_integration.py
|
||
│ │ │ │ └── billing.py
|
||
│ │ │ └── router.py
|
||
│ │ ├── core/ # 核心配置
|
||
│ │ │ ├── config.py
|
||
│ │ │ ├── security.py
|
||
│ │ │ └── dependencies.py
|
||
│ │ ├── db/ # 資料庫
|
||
│ │ │ ├── base.py
|
||
│ │ │ └── session.py
|
||
│ │ ├── models/ # ORM 模型
|
||
│ │ ├── schemas/ # Pydantic Schema
|
||
│ │ ├── services/ # 業務邏輯
|
||
│ │ └── utils/ # 工具函數
|
||
│ ├── alembic/ # 資料庫遷移
|
||
│ ├── tests/ # 測試
|
||
│ ├── .env # 環境變數
|
||
│ ├── requirements.txt # 依賴套件
|
||
│ └── main.py # 應用入口
|
||
│
|
||
├── frontend/ # 前端服務
|
||
│ ├── admin-portal/ # 管理後台
|
||
│ │ ├── app/ # Next.js App Router
|
||
│ │ ├── components/ # React 元件
|
||
│ │ ├── lib/ # 工具函數
|
||
│ │ ├── public/ # 靜態資源
|
||
│ │ └── package.json
|
||
│ │
|
||
│ └── landing-page/ # 行銷頁面
|
||
│ ├── app/
|
||
│ ├── components/
|
||
│ └── package.json
|
||
│
|
||
└── docs/ # 文件
|
||
├── architecture/ # 架構設計
|
||
├── api-specs/ # API 規格
|
||
├── business/ # 商業計畫
|
||
└── deployment/ # 部署文件
|
||
```
|
||
|
||
## 開發環境
|
||
|
||
### 後端環境
|
||
|
||
**Python 版本**: 3.11+
|
||
|
||
**Port 配置**:
|
||
- 開發環境: `10281`
|
||
- 測試環境: `10282`
|
||
|
||
**資料庫**:
|
||
- Host: `10.1.0.20`
|
||
- Port: `5433`
|
||
- Database: `virtual_mis`
|
||
- User: `admin`
|
||
|
||
**啟動方式**:
|
||
```bash
|
||
cd D:\_Develop\porscheworld_develop\virtual-mis\backend
|
||
START_BACKEND.bat
|
||
```
|
||
|
||
### 前端環境
|
||
|
||
**Node.js 版本**: 20+
|
||
|
||
**Port 配置**:
|
||
- Admin Portal: `10280`
|
||
- Landing Page: `10290`
|
||
|
||
**啟動方式**:
|
||
```bash
|
||
cd D:\_Develop\porscheworld_develop\virtual-mis\frontend\admin-portal
|
||
START_FRONTEND.bat
|
||
```
|
||
|
||
## 編碼規範
|
||
|
||
### Python (後端)
|
||
|
||
1. **命名規範**:
|
||
- 檔案名稱: `snake_case.py`
|
||
- 類別名稱: `PascalCase`
|
||
- 函數名稱: `snake_case`
|
||
- 常數: `UPPER_CASE`
|
||
|
||
2. **程式碼風格**:
|
||
- 使用 Black 格式化
|
||
- 遵循 PEP 8
|
||
- 最大行長: 100 字元
|
||
|
||
3. **型別標註**:
|
||
```python
|
||
def get_tenant(tenant_id: int) -> Optional[Tenant]:
|
||
"""取得租戶資料"""
|
||
pass
|
||
```
|
||
|
||
4. **文件字串**:
|
||
```python
|
||
def create_tenant(data: TenantCreate) -> Tenant:
|
||
"""
|
||
建立新租戶
|
||
|
||
Args:
|
||
data: 租戶建立資料
|
||
|
||
Returns:
|
||
Tenant: 建立的租戶物件
|
||
|
||
Raises:
|
||
ValueError: 租戶代碼已存在
|
||
"""
|
||
pass
|
||
```
|
||
|
||
### TypeScript (前端)
|
||
|
||
1. **命名規範**:
|
||
- 檔案名稱: `kebab-case.tsx`
|
||
- 元件名稱: `PascalCase`
|
||
- 函數名稱: `camelCase`
|
||
- 介面: `PascalCase` (前綴 I 可選)
|
||
|
||
2. **元件結構**:
|
||
```typescript
|
||
'use client'
|
||
|
||
import { useState } from 'react'
|
||
|
||
interface Props {
|
||
title: string
|
||
onSubmit: (data: FormData) => void
|
||
}
|
||
|
||
export default function MyComponent({ title, onSubmit }: Props) {
|
||
const [loading, setLoading] = useState(false)
|
||
|
||
return (
|
||
<div>
|
||
<h1>{title}</h1>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
3. **型別定義**:
|
||
```typescript
|
||
// types/tenant.ts
|
||
export interface Tenant {
|
||
id: number
|
||
code: string
|
||
name: string
|
||
status: 'trial' | 'active' | 'suspended'
|
||
}
|
||
```
|
||
|
||
## API 設計規範
|
||
|
||
### RESTful 規範
|
||
|
||
**URL 命名**:
|
||
- 使用名詞複數: `/api/v1/tenants`
|
||
- 使用 kebab-case: `/api/v1/tenant-onboarding`
|
||
- 版本控制: `/api/v1/`, `/api/v2/`
|
||
|
||
**HTTP 方法**:
|
||
- `GET`: 查詢資料
|
||
- `POST`: 建立資料
|
||
- `PUT`: 完整更新
|
||
- `PATCH`: 部分更新
|
||
- `DELETE`: 刪除資料
|
||
|
||
**回應格式**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": 1,
|
||
"name": "測試公司"
|
||
},
|
||
"message": "操作成功"
|
||
}
|
||
```
|
||
|
||
**錯誤回應**:
|
||
```json
|
||
{
|
||
"success": false,
|
||
"error": {
|
||
"code": "TENANT_NOT_FOUND",
|
||
"message": "找不到指定的租戶",
|
||
"details": {}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Schema 設計
|
||
|
||
**命名規範**:
|
||
- Base Schema: `TenantBase`
|
||
- Create Schema: `TenantCreate`
|
||
- Update Schema: `TenantUpdate`
|
||
- Response Schema: `TenantResponse`
|
||
|
||
**範例**:
|
||
```python
|
||
from pydantic import BaseModel, Field
|
||
|
||
class TenantBase(BaseModel):
|
||
"""租戶基礎 Schema"""
|
||
code: str = Field(..., max_length=50, description="租戶代碼")
|
||
name: str = Field(..., max_length=200, description="公司名稱")
|
||
|
||
class TenantCreate(TenantBase):
|
||
"""建立租戶 Schema"""
|
||
admin_email: str = Field(..., description="管理員郵箱")
|
||
|
||
class TenantResponse(TenantBase):
|
||
"""租戶回應 Schema"""
|
||
id: int
|
||
status: str
|
||
created_at: datetime
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
```
|
||
|
||
## 資料庫規範
|
||
|
||
### Migration 管理
|
||
|
||
**命名規則**:
|
||
```
|
||
{timestamp}_{description}.py
|
||
例如: 20260227_create_subscriptions_table.py
|
||
```
|
||
|
||
**Migration 內容**:
|
||
```python
|
||
def upgrade() -> None:
|
||
"""升級操作"""
|
||
op.create_table(
|
||
'subscriptions',
|
||
sa.Column('id', sa.Integer(), primary_key=True),
|
||
sa.Column('tenant_id', sa.Integer(), nullable=False),
|
||
# ...
|
||
)
|
||
|
||
def downgrade() -> None:
|
||
"""降級操作"""
|
||
op.drop_table('subscriptions')
|
||
```
|
||
|
||
### 表格命名
|
||
|
||
- 使用複數形式: `subscriptions`, `invoices`
|
||
- 使用 snake_case
|
||
- 加入前綴表示模組: `billing_invoices`
|
||
|
||
## Git 工作流程
|
||
|
||
### 分支策略
|
||
|
||
- `master`: 生產環境
|
||
- `develop`: 開發環境
|
||
- `feature/*`: 功能開發
|
||
- `hotfix/*`: 緊急修復
|
||
|
||
### Commit 訊息
|
||
|
||
**格式**:
|
||
```
|
||
<類型>: <簡短描述>
|
||
|
||
<詳細說明>
|
||
|
||
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
||
```
|
||
|
||
**類型**:
|
||
- `feat`: 新功能
|
||
- `fix`: 錯誤修復
|
||
- `docs`: 文件更新
|
||
- `refactor`: 重構
|
||
- `test`: 測試
|
||
- `chore`: 雜項
|
||
|
||
**範例**:
|
||
```
|
||
feat: 新增租戶開通 API
|
||
|
||
實作租戶自動開通功能,包含:
|
||
- 建立租戶資料
|
||
- 配置網域
|
||
- 建立 Keycloak Realm
|
||
- 初始化服務
|
||
|
||
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
||
```
|
||
|
||
## 測試規範
|
||
|
||
### 單元測試
|
||
|
||
**覆蓋率目標**: 80%+
|
||
|
||
**測試檔案命名**:
|
||
```
|
||
tests/
|
||
├── unit/
|
||
│ ├── test_tenant_service.py
|
||
│ └── test_billing_service.py
|
||
└── integration/
|
||
├── test_api_tenants.py
|
||
└── test_api_billing.py
|
||
```
|
||
|
||
**測試範例**:
|
||
```python
|
||
import pytest
|
||
from app.services.tenant_service import create_tenant
|
||
|
||
def test_create_tenant_success():
|
||
"""測試建立租戶成功"""
|
||
data = TenantCreate(
|
||
code="testcompany",
|
||
name="測試公司",
|
||
admin_email="admin@test.com"
|
||
)
|
||
|
||
tenant = create_tenant(data)
|
||
|
||
assert tenant.code == "testcompany"
|
||
assert tenant.status == "trial"
|
||
```
|
||
|
||
## 安全規範
|
||
|
||
1. **環境變數**: 所有敏感資訊放在 `.env`
|
||
2. **密碼處理**: 使用 bcrypt 雜湊
|
||
3. **API 驗證**: 所有 API 需要 JWT token
|
||
4. **輸入驗證**: 使用 Pydantic 驗證所有輸入
|
||
5. **SQL 注入**: 使用 ORM,避免原生 SQL
|
||
|
||
## 文件規範
|
||
|
||
1. **README.md**: 每個專案必須包含
|
||
2. **API 文件**: 使用 FastAPI 自動生成 (Swagger)
|
||
3. **程式碼註解**: 複雜邏輯必須註解
|
||
4. **設計文件**: 重要功能需要設計文件
|
||
|
||
---
|
||
|
||
**參考資源**:
|
||
- [FastAPI 官方文件](https://fastapi.tiangolo.com/)
|
||
- [Next.js 官方文件](https://nextjs.org/docs)
|
||
- [Python PEP 8](https://peps.python.org/pep-0008/)
|