diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..68486bc --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(dir)" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/README.md b/README.md index 300b8d6..b207b72 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,20 @@ ## 🛠 技術架構 +### 端口配置 + +#### 主要服務端口 +- **前端服務**:Port 12012 (Next.js) +- **後端 API**:Port 12011 (Flask) +- **資料庫**:Port 33306 (MySQL) + +#### 外部依賴端口 +- **快取系統**:Port 6379 (Redis) +- **LDAP 服務**:Port 389 +- **郵件服務**:Port 25 (SMTP) + +> 📝 **註意**:所有主要服務端口已調整到 12010-12019 範圍內,符合企業環境統一規範。 + ### 前端技術棧 - **框架**:Next.js 14 (React 18) - **UI 庫**:Material-UI (MUI) 5 @@ -100,7 +114,7 @@ copy .env.example .env # 初始化資料庫 python init_db.py -# 啟動後端服務 +# 啟動後端服務 (現使用 port 12011) python app.py ``` @@ -116,8 +130,12 @@ npm install copy .env.example .env.local # 編輯 .env.local 文件 -# 啟動開發服務器 +# 啟動開發服務器 (現使用 port 12012) npm run dev + +# 生產環境部署 +npm run build +npm run start ``` ### 4. 背景任務 (可選) @@ -139,7 +157,7 @@ celery -A celery_app beat --loglevel=info # 資料庫連線 DATABASE_URL=mysql+pymysql://username:password@host:port/database MYSQL_HOST=mysql.example.com -MYSQL_PORT=3306 +MYSQL_PORT=33306 MYSQL_USER=your_user MYSQL_PASSWORD=your_password MYSQL_DATABASE=your_database @@ -162,33 +180,68 @@ SMTP_SENDER_EMAIL=noreply@example.com # Redis 設定 REDIS_URL=redis://localhost:6379/0 + +# CORS 設定 +CORS_ORIGINS=http://localhost:12012 ``` #### 前端設定 (frontend/.env.local) ```env -NEXT_PUBLIC_API_BASE_URL=http://localhost:5000/api +# 後端 API 設定 +NEXT_PUBLIC_API_URL=http://localhost:12011 +NEXT_PUBLIC_BACKEND_URL=http://localhost:12011 + +# 應用基本設定 NEXT_PUBLIC_APP_NAME=PANJIT To-Do System +NODE_ENV=development + +# CORS 設定 +NEXT_PUBLIC_ALLOWED_ORIGINS=http://localhost:12012,http://localhost:12011 ``` ## 📝 部署指南 +### Docker 部署 (建議) + +```bash +# 建置 Docker 鏡像 +# 後端 +cd backend +docker build -t todolist-backend . + +# 前端 +cd frontend +docker build -t todolist-frontend . + +# 啟動容器 +docker run -d -p 12011:12011 --name backend todolist-backend +docker run -d -p 12012:12012 --name frontend todolist-frontend +``` + ### 生產環境部署 1. **資料庫準備** - - 建立 MySQL 資料庫 + - 建立 MySQL 資料庫 (Port 33306) - 執行資料庫遷移腳本 - 設定資料庫備份策略 2. **應用程式部署** - - 設定 IIS 或 Apache 反向代理 + - 設定反向代理 (Nginx/IIS) + - 前端: Port 12012 → 對外 Port 80/443 + - 後端 API: Port 12011 → 對外 API 路徑 - 配置 SSL 證書 - - 設定環境變數 + - 設定生產環境變數 3. **背景服務設定** - - 配置 Celery Windows Service - - 設定 Redis 服務自動啟動 + - 配置 Celery Windows Service + - 設定 Redis 服務自動啟動 (Port 6379) - 配置日誌輪轉 +4. **環境判斷保護** + - 生產環境使用 `NODE_ENV=production` + - 自動禁用 HMR WebSocket 連接 + - 禁用 React DevTools 提示 + ## 🔐 權限矩陣 ### 待辦事項權限控制 @@ -294,7 +347,16 @@ A: 檢查 LDAP 設定和網路連線,確認服務帳號權限 A: 驗證 SMTP 設定,檢查防火牆和郵件伺服器設定 **Q: 前端無法連接後端 API** -A: 確認 CORS 設定和 API 基礎 URL 配置 +A: 確認 CORS 設定和 API 基礎 URL 配置,檢查端口 12011 (後端) 和 12012 (前端) 是否正確設定 + +**Q: 生產環境出現 WebSocket HMR 錯誤** +A: 系統已加入環境判斷保護,確保使用 `npm run build && npm run start` 而非 `npm run dev` 進行生產部署 + +**Q: 端口衝突問題** +A: 系統使用 12010-12019 範圍的端口,如遇衝突請修改環境變數中的端口設定 + +**Q: CORS 錯誤** +A: 確認後端 .env 文件中 CORS_ORIGINS 包含正確的前端 URL (http://localhost:12012) **Q: Excel 匯入失敗** A: 檢查文件格式和欄位映射,參考匯入模板 diff --git a/backend/.env b/backend/.env index 0727c2d..c4176cc 100644 --- a/backend/.env +++ b/backend/.env @@ -72,7 +72,7 @@ REDIS_URL=redis://localhost:6379/0 # =========================================== # CORS 設定 # =========================================== -CORS_ORIGINS=http://localhost:3000,http://localhost:3001,http://localhost:3002 +CORS_ORIGINS=http://localhost:12012,http://localhost:3001,http://localhost:3002 # =========================================== # 日誌設定 diff --git a/backend/.env.example b/backend/.env.example index c1223f2..696d9eb 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -83,7 +83,7 @@ REDIS_URL=redis://localhost:6379/0 # =========================================== # CORS 設定 # =========================================== -CORS_ORIGINS=http://localhost:3000 +CORS_ORIGINS=http://localhost:12012 # =========================================== # 日誌設定 diff --git a/backend/Dockerfile b/backend/Dockerfile index 08e4b3e..8324d71 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -30,7 +30,7 @@ ENV FLASK_APP=app.py ENV PYTHONUNBUFFERED=1 # Expose port -EXPOSE 5000 +EXPOSE 12011 # Run the application -CMD ["python", "-m", "flask", "run", "--host=0.0.0.0", "--port=5000"] \ No newline at end of file +CMD ["python", "-m", "flask", "run", "--host=0.0.0.0", "--port=12011"] \ No newline at end of file diff --git a/backend/app.py b/backend/app.py index 3742384..5a3e2ab 100644 --- a/backend/app.py +++ b/backend/app.py @@ -157,4 +157,4 @@ def register_error_handlers(app): if __name__ == '__main__': app = create_app() - app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file + app.run(host='0.0.0.0', port=12011, debug=True) \ No newline at end of file diff --git a/frontend/.env.example b/frontend/.env.example index 0a88aa5..b206d7b 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -15,8 +15,8 @@ NEXT_PUBLIC_APP_VERSION="1.0.0" # =========================================== # 後端 API 基本網址 -NEXT_PUBLIC_API_URL=http://localhost:5000 -NEXT_PUBLIC_BACKEND_URL=http://localhost:5000 +NEXT_PUBLIC_API_URL=http://localhost:12011 +NEXT_PUBLIC_BACKEND_URL=http://localhost:12011 # API 版本 NEXT_PUBLIC_API_VERSION=v1 @@ -146,7 +146,7 @@ NEXT_PUBLIC_REACT_QUERY_DEVTOOLS=true # =========================================== # CORS 設定 (僅供參考,實際由後端控制) -NEXT_PUBLIC_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5000 +NEXT_PUBLIC_ALLOWED_ORIGINS=http://localhost:12012,http://localhost:12011 # CSP 設定提示 NEXT_PUBLIC_CSP_ENABLED=false diff --git a/frontend/.env.local b/frontend/.env.local index 0a88aa5..b206d7b 100644 --- a/frontend/.env.local +++ b/frontend/.env.local @@ -15,8 +15,8 @@ NEXT_PUBLIC_APP_VERSION="1.0.0" # =========================================== # 後端 API 基本網址 -NEXT_PUBLIC_API_URL=http://localhost:5000 -NEXT_PUBLIC_BACKEND_URL=http://localhost:5000 +NEXT_PUBLIC_API_URL=http://localhost:12011 +NEXT_PUBLIC_BACKEND_URL=http://localhost:12011 # API 版本 NEXT_PUBLIC_API_VERSION=v1 @@ -146,7 +146,7 @@ NEXT_PUBLIC_REACT_QUERY_DEVTOOLS=true # =========================================== # CORS 設定 (僅供參考,實際由後端控制) -NEXT_PUBLIC_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5000 +NEXT_PUBLIC_ALLOWED_ORIGINS=http://localhost:12012,http://localhost:12011 # CSP 設定提示 NEXT_PUBLIC_CSP_ENABLED=false diff --git a/frontend/Dockerfile b/frontend/Dockerfile index d964c24..31e615c 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -35,9 +35,9 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs -EXPOSE 3000 +EXPOSE 12012 -ENV PORT 3000 +ENV PORT 12012 ENV HOSTNAME "0.0.0.0" CMD ["node", "server.js"] \ No newline at end of file diff --git a/frontend/next.config.js b/frontend/next.config.js index 194e9c0..ca1e07c 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -1,16 +1,59 @@ +const crypto = require('crypto') + /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, swcMinify: true, output: 'standalone', env: { - NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000', + NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:12011', + }, + webpack: (config, { dev, isServer }) => { + // 在生產環境中禁用 HMR 相關功能 + if (!dev && !isServer) { + config.optimization = { + ...config.optimization, + splitChunks: { + chunks: 'all', + cacheGroups: { + default: false, + vendors: false, + framework: { + chunks: 'all', + name: 'framework', + test: /(? 160000 && /node_modules[/\\]/.test(module.identifier()) + }, + name(module) { + const hash = crypto.createHash('sha1') + hash.update(module.identifier()) + return hash.digest('hex').substring(0, 8) + }, + priority: 30, + minChunks: 1, + reuseExistingChunk: true, + }, + commons: { + name: 'commons', + minChunks: 2, + priority: 20, + }, + }, + }, + } + } + return config }, async rewrites() { return [ { source: '/api/:path*', - destination: `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000'}/api/:path*`, + destination: `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:12011'}/api/:path*`, }, ] }, diff --git a/frontend/package.json b/frontend/package.json index e47ca22..c074130 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,9 +3,9 @@ "version": "1.0.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev -p 12012", "build": "next build", - "start": "next start", + "start": "next start -p 12012", "lint": "next lint", "type-check": "tsc --noEmit" }, diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 2409380..7c22b4a 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata, Viewport } from 'next'; import { Inter } from 'next/font/google'; import { Providers } from '@/providers'; +import EnvironmentWrapper from '@/components/EnvironmentWrapper'; import './globals.css'; const inter = Inter({ subsets: ['latin'] }); @@ -31,9 +32,11 @@ export default function RootLayout({ return (
-