7th_fix port
This commit is contained in:
9
.claude/settings.local.json
Normal file
9
.claude/settings.local.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(dir)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
82
README.md
82
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)
|
- **框架**:Next.js 14 (React 18)
|
||||||
- **UI 庫**:Material-UI (MUI) 5
|
- **UI 庫**:Material-UI (MUI) 5
|
||||||
@@ -100,7 +114,7 @@ copy .env.example .env
|
|||||||
# 初始化資料庫
|
# 初始化資料庫
|
||||||
python init_db.py
|
python init_db.py
|
||||||
|
|
||||||
# 啟動後端服務
|
# 啟動後端服務 (現使用 port 12011)
|
||||||
python app.py
|
python app.py
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -116,8 +130,12 @@ npm install
|
|||||||
copy .env.example .env.local
|
copy .env.example .env.local
|
||||||
# 編輯 .env.local 文件
|
# 編輯 .env.local 文件
|
||||||
|
|
||||||
# 啟動開發服務器
|
# 啟動開發服務器 (現使用 port 12012)
|
||||||
npm run dev
|
npm run dev
|
||||||
|
|
||||||
|
# 生產環境部署
|
||||||
|
npm run build
|
||||||
|
npm run start
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. 背景任務 (可選)
|
### 4. 背景任務 (可選)
|
||||||
@@ -139,7 +157,7 @@ celery -A celery_app beat --loglevel=info
|
|||||||
# 資料庫連線
|
# 資料庫連線
|
||||||
DATABASE_URL=mysql+pymysql://username:password@host:port/database
|
DATABASE_URL=mysql+pymysql://username:password@host:port/database
|
||||||
MYSQL_HOST=mysql.example.com
|
MYSQL_HOST=mysql.example.com
|
||||||
MYSQL_PORT=3306
|
MYSQL_PORT=33306
|
||||||
MYSQL_USER=your_user
|
MYSQL_USER=your_user
|
||||||
MYSQL_PASSWORD=your_password
|
MYSQL_PASSWORD=your_password
|
||||||
MYSQL_DATABASE=your_database
|
MYSQL_DATABASE=your_database
|
||||||
@@ -162,33 +180,68 @@ SMTP_SENDER_EMAIL=noreply@example.com
|
|||||||
|
|
||||||
# Redis 設定
|
# Redis 設定
|
||||||
REDIS_URL=redis://localhost:6379/0
|
REDIS_URL=redis://localhost:6379/0
|
||||||
|
|
||||||
|
# CORS 設定
|
||||||
|
CORS_ORIGINS=http://localhost:12012
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 前端設定 (frontend/.env.local)
|
#### 前端設定 (frontend/.env.local)
|
||||||
```env
|
```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
|
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. **資料庫準備**
|
1. **資料庫準備**
|
||||||
- 建立 MySQL 資料庫
|
- 建立 MySQL 資料庫 (Port 33306)
|
||||||
- 執行資料庫遷移腳本
|
- 執行資料庫遷移腳本
|
||||||
- 設定資料庫備份策略
|
- 設定資料庫備份策略
|
||||||
|
|
||||||
2. **應用程式部署**
|
2. **應用程式部署**
|
||||||
- 設定 IIS 或 Apache 反向代理
|
- 設定反向代理 (Nginx/IIS)
|
||||||
|
- 前端: Port 12012 → 對外 Port 80/443
|
||||||
|
- 後端 API: Port 12011 → 對外 API 路徑
|
||||||
- 配置 SSL 證書
|
- 配置 SSL 證書
|
||||||
- 設定環境變數
|
- 設定生產環境變數
|
||||||
|
|
||||||
3. **背景服務設定**
|
3. **背景服務設定**
|
||||||
- 配置 Celery Windows Service
|
- 配置 Celery Windows Service
|
||||||
- 設定 Redis 服務自動啟動
|
- 設定 Redis 服務自動啟動 (Port 6379)
|
||||||
- 配置日誌輪轉
|
- 配置日誌輪轉
|
||||||
|
|
||||||
|
4. **環境判斷保護**
|
||||||
|
- 生產環境使用 `NODE_ENV=production`
|
||||||
|
- 自動禁用 HMR WebSocket 連接
|
||||||
|
- 禁用 React DevTools 提示
|
||||||
|
|
||||||
## 🔐 權限矩陣
|
## 🔐 權限矩陣
|
||||||
|
|
||||||
### 待辦事項權限控制
|
### 待辦事項權限控制
|
||||||
@@ -294,7 +347,16 @@ A: 檢查 LDAP 設定和網路連線,確認服務帳號權限
|
|||||||
A: 驗證 SMTP 設定,檢查防火牆和郵件伺服器設定
|
A: 驗證 SMTP 設定,檢查防火牆和郵件伺服器設定
|
||||||
|
|
||||||
**Q: 前端無法連接後端 API**
|
**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 匯入失敗**
|
**Q: Excel 匯入失敗**
|
||||||
A: 檢查文件格式和欄位映射,參考匯入模板
|
A: 檢查文件格式和欄位映射,參考匯入模板
|
||||||
|
@@ -72,7 +72,7 @@ REDIS_URL=redis://localhost:6379/0
|
|||||||
# ===========================================
|
# ===========================================
|
||||||
# CORS 設定
|
# CORS 設定
|
||||||
# ===========================================
|
# ===========================================
|
||||||
CORS_ORIGINS=http://localhost:3000,http://localhost:3001,http://localhost:3002
|
CORS_ORIGINS=http://localhost:12012,http://localhost:3001,http://localhost:3002
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# 日誌設定
|
# 日誌設定
|
||||||
|
@@ -83,7 +83,7 @@ REDIS_URL=redis://localhost:6379/0
|
|||||||
# ===========================================
|
# ===========================================
|
||||||
# CORS 設定
|
# CORS 設定
|
||||||
# ===========================================
|
# ===========================================
|
||||||
CORS_ORIGINS=http://localhost:3000
|
CORS_ORIGINS=http://localhost:12012
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# 日誌設定
|
# 日誌設定
|
||||||
|
@@ -30,7 +30,7 @@ ENV FLASK_APP=app.py
|
|||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 5000
|
EXPOSE 12011
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
CMD ["python", "-m", "flask", "run", "--host=0.0.0.0", "--port=5000"]
|
CMD ["python", "-m", "flask", "run", "--host=0.0.0.0", "--port=12011"]
|
@@ -157,4 +157,4 @@ def register_error_handlers(app):
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = create_app()
|
app = create_app()
|
||||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
app.run(host='0.0.0.0', port=12011, debug=True)
|
@@ -15,8 +15,8 @@ NEXT_PUBLIC_APP_VERSION="1.0.0"
|
|||||||
# ===========================================
|
# ===========================================
|
||||||
|
|
||||||
# 後端 API 基本網址
|
# 後端 API 基本網址
|
||||||
NEXT_PUBLIC_API_URL=http://localhost:5000
|
NEXT_PUBLIC_API_URL=http://localhost:12011
|
||||||
NEXT_PUBLIC_BACKEND_URL=http://localhost:5000
|
NEXT_PUBLIC_BACKEND_URL=http://localhost:12011
|
||||||
|
|
||||||
# API 版本
|
# API 版本
|
||||||
NEXT_PUBLIC_API_VERSION=v1
|
NEXT_PUBLIC_API_VERSION=v1
|
||||||
@@ -146,7 +146,7 @@ NEXT_PUBLIC_REACT_QUERY_DEVTOOLS=true
|
|||||||
# ===========================================
|
# ===========================================
|
||||||
|
|
||||||
# CORS 設定 (僅供參考,實際由後端控制)
|
# CORS 設定 (僅供參考,實際由後端控制)
|
||||||
NEXT_PUBLIC_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5000
|
NEXT_PUBLIC_ALLOWED_ORIGINS=http://localhost:12012,http://localhost:12011
|
||||||
|
|
||||||
# CSP 設定提示
|
# CSP 設定提示
|
||||||
NEXT_PUBLIC_CSP_ENABLED=false
|
NEXT_PUBLIC_CSP_ENABLED=false
|
||||||
|
@@ -15,8 +15,8 @@ NEXT_PUBLIC_APP_VERSION="1.0.0"
|
|||||||
# ===========================================
|
# ===========================================
|
||||||
|
|
||||||
# 後端 API 基本網址
|
# 後端 API 基本網址
|
||||||
NEXT_PUBLIC_API_URL=http://localhost:5000
|
NEXT_PUBLIC_API_URL=http://localhost:12011
|
||||||
NEXT_PUBLIC_BACKEND_URL=http://localhost:5000
|
NEXT_PUBLIC_BACKEND_URL=http://localhost:12011
|
||||||
|
|
||||||
# API 版本
|
# API 版本
|
||||||
NEXT_PUBLIC_API_VERSION=v1
|
NEXT_PUBLIC_API_VERSION=v1
|
||||||
@@ -146,7 +146,7 @@ NEXT_PUBLIC_REACT_QUERY_DEVTOOLS=true
|
|||||||
# ===========================================
|
# ===========================================
|
||||||
|
|
||||||
# CORS 設定 (僅供參考,實際由後端控制)
|
# CORS 設定 (僅供參考,實際由後端控制)
|
||||||
NEXT_PUBLIC_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5000
|
NEXT_PUBLIC_ALLOWED_ORIGINS=http://localhost:12012,http://localhost:12011
|
||||||
|
|
||||||
# CSP 設定提示
|
# CSP 設定提示
|
||||||
NEXT_PUBLIC_CSP_ENABLED=false
|
NEXT_PUBLIC_CSP_ENABLED=false
|
||||||
|
@@ -35,9 +35,9 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|||||||
|
|
||||||
USER nextjs
|
USER nextjs
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 12012
|
||||||
|
|
||||||
ENV PORT 3000
|
ENV PORT 12012
|
||||||
ENV HOSTNAME "0.0.0.0"
|
ENV HOSTNAME "0.0.0.0"
|
||||||
|
|
||||||
CMD ["node", "server.js"]
|
CMD ["node", "server.js"]
|
@@ -1,16 +1,59 @@
|
|||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
swcMinify: true,
|
swcMinify: true,
|
||||||
output: 'standalone',
|
output: 'standalone',
|
||||||
env: {
|
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: /(?<!node_modules.*)[\\/]node_modules[\\/](react|react-dom|scheduler|prop-types|use-subscription)[\\/]/,
|
||||||
|
priority: 40,
|
||||||
|
enforce: true,
|
||||||
|
},
|
||||||
|
lib: {
|
||||||
|
test(module) {
|
||||||
|
return module.size() > 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() {
|
async rewrites() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: '/api/:path*',
|
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*`,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@@ -3,9 +3,9 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev -p 12012",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start -p 12012",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import type { Metadata, Viewport } from 'next';
|
import type { Metadata, Viewport } from 'next';
|
||||||
import { Inter } from 'next/font/google';
|
import { Inter } from 'next/font/google';
|
||||||
import { Providers } from '@/providers';
|
import { Providers } from '@/providers';
|
||||||
|
import EnvironmentWrapper from '@/components/EnvironmentWrapper';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] });
|
const inter = Inter({ subsets: ['latin'] });
|
||||||
@@ -31,9 +32,11 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="zh-TW" suppressHydrationWarning>
|
<html lang="zh-TW" suppressHydrationWarning>
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<Providers>
|
<EnvironmentWrapper>
|
||||||
{children}
|
<Providers>
|
||||||
</Providers>
|
{children}
|
||||||
|
</Providers>
|
||||||
|
</EnvironmentWrapper>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
77
frontend/src/components/EnvironmentWrapper.tsx
Normal file
77
frontend/src/components/EnvironmentWrapper.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
interface EnvironmentWrapperProps {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EnvironmentWrapper({ children }: EnvironmentWrapperProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
// 在生產環境中禁用 HMR 和開發工具
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
// 禁用 React DevTools 提示
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const consoleWarn = console.warn
|
||||||
|
const consoleLog = console.log
|
||||||
|
|
||||||
|
console.warn = (...args: any[]) => {
|
||||||
|
const message = args[0]
|
||||||
|
if (typeof message === 'string' &&
|
||||||
|
(message.includes('Download the React DevTools') ||
|
||||||
|
message.includes('React DevTools'))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
consoleWarn.apply(console, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log = (...args: any[]) => {
|
||||||
|
const message = args[0]
|
||||||
|
if (typeof message === 'string' &&
|
||||||
|
(message.includes('Download the React DevTools') ||
|
||||||
|
message.includes('React DevTools'))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
consoleLog.apply(console, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止 HMR WebSocket 連接嘗試
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const originalWebSocket = window.WebSocket
|
||||||
|
window.WebSocket = class extends WebSocket {
|
||||||
|
constructor(url: string | URL, protocols?: string | string[]) {
|
||||||
|
const urlString = url.toString()
|
||||||
|
|
||||||
|
// 阻止 HMR 相關的 WebSocket 連接
|
||||||
|
if (urlString.includes('_next/webpack-hmr') ||
|
||||||
|
urlString.includes('sockjs-node') ||
|
||||||
|
urlString.includes('hot-update')) {
|
||||||
|
console.warn('HMR WebSocket connection blocked in production environment')
|
||||||
|
// 創建一個假的 WebSocket 以避免錯誤
|
||||||
|
super('ws://localhost:1')
|
||||||
|
this.close()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
super(url, protocols)
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 環境檢查和警告
|
||||||
|
if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') {
|
||||||
|
console.info('🔧 Development mode detected')
|
||||||
|
console.info('📍 Frontend running on port:', window.location.port)
|
||||||
|
console.info('🔗 API URL:', process.env.NEXT_PUBLIC_API_URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production' && typeof window !== 'undefined') {
|
||||||
|
console.info('🚀 Production mode detected')
|
||||||
|
console.info('✅ HMR and dev tools have been disabled')
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <>{children}</>
|
||||||
|
}
|
Reference in New Issue
Block a user