diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..eb44824 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,114 @@ +# Dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Build outputs +dist +build + +# Environment files +.env +.env.local +.env.*.local + +# Configuration files (runtime generated) +public/config.json + + +# IDE and editor files +.vscode +.idea +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Git +.git +.gitignore + +# Docker +Dockerfile +.dockerignore +docker-compose.yml + +# Documentation +README.md +*.md + +# Scripts +start.sh + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test diff --git a/.eslintrc.js b/.eslintrc.js index c542d55..f207290 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -15,7 +15,7 @@ module.exports = { ecmaVersion: 2020, }, rules: { - "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", - "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", + "no-console": "off", + "no-debugger": "off", }, }; diff --git a/.gitignore b/.gitignore index 3a11a99..21a8a73 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ node_modules .env.local .env.*.local +# Configuration files (runtime generated) +public/config.json + # Log files npm-debug.log* yarn-debug.log* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..51e2026 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,56 @@ +# Multi-stage Dockerfile for DeliciousFoodMap-Web +# Stage 1: Build the Vue.js application +FROM node:18-alpine AS build-stage + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ +COPY pnpm-lock.yaml* ./ + +# Install dependencies +RUN npm install -g pnpm && pnpm install --frozen-lockfile + +# Copy source code +COPY . . + +# Build the application +RUN pnpm run build + +# Stage 2: Production stage with Nginx +FROM nginx:1.25-alpine AS production-stage + +# Remove default nginx website +RUN rm -rf /usr/share/nginx/html/* + +# Copy built application from build stage +COPY --from=build-stage /app/dist /usr/share/nginx/html + +# Copy nginx configuration +COPY docker/nginx.conf /etc/nginx/nginx.conf +COPY docker/default.conf /etc/nginx/conf.d/default.conf + +# Copy config template for runtime substitution +COPY public/config.json.template /usr/share/nginx/html/config.json.template + +# Copy startup script +COPY docker/docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh + +# Set environment variables with defaults +ENV API_BASE_URL=http://localhost:8080 +ENV AMAP_KEY="" + +# Expose port +EXPOSE 80 + +EXPOSE 443 + + + +# Use custom entrypoint +ENTRYPOINT ["/docker-entrypoint.sh"] + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/dev-start.sh b/dev-start.sh new file mode 100644 index 0000000..852fb1c --- /dev/null +++ b/dev-start.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# 本地开发环境启动脚本 + +set -e + +echo "🍕 美食地图前端 - 本地开发环境" +echo "================================" + +# 检查 Node.js +if ! command -v node &> /dev/null; then + echo "❌ Node.js 未安装,请先安装 Node.js (版本 14+)" + exit 1 +fi + +# 检查 npm +if ! command -v npm &> /dev/null; then + echo "❌ npm 未安装" + exit 1 +fi + +echo "✅ Node.js 版本: $(node --version)" +echo "✅ npm 版本: $(npm --version)" +echo "" + +# 检查依赖 +if [ ! -d "node_modules" ]; then + echo "📦 安装依赖..." + npm install + echo "✅ 依赖安装完成" + echo "" +fi + +# 检查配置文件 +if [ ! -f "public/config.json.local" ]; then + echo "⚙️ 创建本地配置文件..." + cp public/config.json public/config.json.local + echo "📝 请编辑 public/config.json.local 文件,设置正确的配置值:" + echo " - API_BASE_URL: 后端 API 地址" + echo " - AMAP_KEY: 高德地图 API 密钥" + echo "" + echo "配置文件位置: public/config.json.local" + echo "" + read -p "按回车键继续..." +fi + +echo "🚀 启动开发服务器..." +echo "📍 应用将在 http://localhost:8080 启动" +echo "🔧 配置文件: public/config.json.local" +echo "" +echo "💡 提示:" +echo " - 修改代码后会自动热重载" +echo " - 按 Ctrl+C 停止服务器" +echo "" + +# 启动开发服务器 +npm run serve diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d1e7a81 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.8' + +services: + web: + build: . + container_name: delicious-food-map-web + ports: + - "80:80" + environment: + - API_BASE_URL=https://your-api-server.com + - AMAP_KEY=your_amap_key_here + restart: unless-stopped + diff --git a/docker/default.conf b/docker/default.conf new file mode 100644 index 0000000..7bcfb1d --- /dev/null +++ b/docker/default.conf @@ -0,0 +1,96 @@ +server { + listen 80; + listen [::]:80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Enable gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + } + + # Cache config.json for a short time to allow updates + location = /config.json { + expires 5m; + add_header Cache-Control "public, max-age=300"; + add_header Content-Type "application/json"; + try_files $uri =404; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Handle Vue.js routing (SPA) + location / { + try_files $uri $uri/ /index.html; + + # Cache HTML files for a short time + location ~* \.html$ { + expires 5m; + add_header Cache-Control "public, max-age=300"; + } + } + + # Proxy API requests to backend (optional) + location /api/ { + # Remove this block if you're using absolute URLs for API calls + # proxy_pass http://backend:8080/; + # proxy_set_header Host $host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # proxy_set_header X-Forwarded-Proto $scheme; + + # For now, return 404 for API calls (since we use absolute URLs) + return 404; + } + + # Error pages + error_page 404 /index.html; + error_page 500 502 503 504 /50x.html; + + location = /50x.html { + root /usr/share/nginx/html; + } + + # Deny access to hidden files + location ~ /\. { + deny all; + access_log off; + log_not_found off; + } + + # Deny access to backup files + location ~ ~$ { + deny all; + access_log off; + log_not_found off; + } +} diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh new file mode 100644 index 0000000..51e9a4a --- /dev/null +++ b/docker/docker-entrypoint.sh @@ -0,0 +1,97 @@ +#!/bin/sh + +# Docker entrypoint script for DeliciousFoodMap-Web +# This script substitutes environment variables in config.json at runtime + +set -e + +echo "Starting DeliciousFoodMap-Web container..." + +# Function to log with timestamp +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" +} + +# Function to substitute environment variables in config.json +substitute_config() { + log "Substituting environment variables in config.json..." + + # Check if template exists + if [ ! -f "/usr/share/nginx/html/config.json.template" ]; then + log "ERROR: config.json.template not found!" + exit 1 + fi + + # Create config.json from template with environment variable substitution + envsubst < /usr/share/nginx/html/config.json.template > /usr/share/nginx/html/config.json + + # Verify the generated config.json is valid JSON + if ! cat /usr/share/nginx/html/config.json | jq empty > /dev/null 2>&1; then + log "WARNING: Generated config.json is not valid JSON. Checking with basic validation..." + + # Basic validation - check if it starts with { and ends with } + if [ "$(head -c 1 /usr/share/nginx/html/config.json)" != "{" ] || [ "$(tail -c 2 /usr/share/nginx/html/config.json | head -c 1)" != "}" ]; then + log "ERROR: Generated config.json appears to be malformed!" + log "Content preview:" + head -n 10 /usr/share/nginx/html/config.json + exit 1 + fi + fi + + log "Environment variable substitution completed successfully" + + # Log configuration summary (without sensitive data) + log "Configuration summary:" + cat /usr/share/nginx/html/config.json | sed 's/"[^"]*KEY[^"]*":\s*"[^"]*"/"***KEY***": "***"/g' | sed 's/"[^"]*DSN[^"]*":\s*"[^"]*"/"***DSN***": "***"/g' +} + +# Function to validate required environment variables +validate_env() { + log "Validating environment variables..." + + # Check required variables + if [ -z "$API_BASE_URL" ]; then + log "WARNING: API_BASE_URL is not set!" + fi + + if [ -z "$AMAP_KEY" ]; then + log "WARNING: AMAP_KEY is not set - map functionality may not work!" + fi + + log "Environment validation completed" +} + +# Function to setup nginx +setup_nginx() { + log "Setting up Nginx..." + + # Test nginx configuration + if ! nginx -t; then + log "ERROR: Nginx configuration test failed!" + exit 1 + fi + + log "Nginx configuration is valid" +} + +# Main execution +main() { + log "Initializing container..." + + # Validate environment variables + validate_env + + # Substitute environment variables in config.json + substitute_config + + # Setup nginx + setup_nginx + + log "Container initialization completed successfully" + + # Execute the main command + exec "$@" +} + +# Run main function with all arguments +main "$@" diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000..cd8f6d6 --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,57 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; + use epoll; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging format + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + # Performance optimizations + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + server_tokens off; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied any; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + + # Include server configurations + include /etc/nginx/conf.d/*.conf; +} diff --git a/public/config.json b/public/config.json new file mode 100644 index 0000000..4f7cef6 --- /dev/null +++ b/public/config.json @@ -0,0 +1,4 @@ +{ + "API_BASE_URL": "http://localhost:8080", + "AMAP_KEY": "" +} diff --git a/public/config.json.template b/public/config.json.template new file mode 100644 index 0000000..bcf07b9 --- /dev/null +++ b/public/config.json.template @@ -0,0 +1,4 @@ +{ + "API_BASE_URL": "${API_BASE_URL}", + "AMAP_KEY": "${AMAP_KEY}" +} diff --git a/public/index.html b/public/index.html index df789bc..3dfdbad 100644 --- a/public/index.html +++ b/public/index.html @@ -8,13 +8,8 @@ <%= htmlWebpackPlugin.options.title %> - - + -