From 6f850caad1ff2fb0278348010035e72564b6eb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=91=E8=90=8C=E8=8A=BD?= <3205788256@qq.com> Date: Thu, 4 Sep 2025 16:03:09 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BD=91=E9=A1=B5=E6=A1=86=E6=9E=B6=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 59 +++++++++++++++ backend/.env.production | 14 ++++ backend/app.py | 6 +- backend/deploy.bat | 27 +++++++ backend/modules/api_60s.py | 5 +- build_frontend.bat | 6 ++ .../60sapi/热搜榜单/抖音热搜榜/js/script.js | 2 +- frontend/react-app/.env.production | 2 + frontend/react-app/package.json | 3 +- frontend/react-app/public/sw.js | 48 ++++++++++++ frontend/react-app/src/index.js | 13 ++++ frontend/react-app/src/pages/AiModelPage.js | 7 +- frontend/react-app/src/pages/Api60sPage.js | 73 +++---------------- frontend/react-app/src/pages/SmallGamePage.js | 7 +- frontend/react-app/src/utils/api.js | 39 +++++----- nginx-config-example.conf | 72 ++++++++++++++++++ 16 files changed, 287 insertions(+), 96 deletions(-) create mode 100644 backend/.env.production create mode 100644 backend/deploy.bat create mode 100644 build_frontend.bat create mode 100644 frontend/react-app/.env.production create mode 100644 frontend/react-app/public/sw.js create mode 100644 nginx-config-example.conf diff --git a/README.md b/README.md index 169baa46..61b18be9 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ InfoGenie 是一个前后端分离的多功能聚合应用,提供实时数据接口、休闲游戏、AI工具等丰富功能。 +### 🌐 部署环境 + +- **前端部署地址**: https://infogenie.shumengya.top +- **后端部署地址**: https://infogenie.api.shumengya.top + ### 🏗️ 技术架构 - **前端**: React + Styled Components + React Router @@ -48,6 +53,60 @@ cd backend pip install -r requirements.txt ``` +## 🚢 部署指南 + +### 🖥️ 前端部署 + +1. 进入前端目录:`cd frontend/react-app` +2. 安装依赖:`npm install` +3. 构建生产环境应用:`npm run build` +4. 将 `build` 目录下的所有文件上传到前端服务器的网站根目录 + +也可以直接运行 `frontend/react-app/deploy.bat` 脚本进行构建。 + +### ⚙️ 后端部署 + +1. 进入后端目录:`cd backend` +2. 安装依赖:`pip install -r requirements.txt` +3. 配置环境变量或创建 `.env` 文件,包含以下内容: + ``` + MONGO_URI=你的MongoDB连接字符串 + MAIL_USERNAME=你的邮箱地址 + MAIL_PASSWORD=你的邮箱授权码 + SECRET_KEY=你的应用密钥 + SESSION_COOKIE_SECURE=True + ``` +4. 使用 Gunicorn 或 uWSGI 作为 WSGI 服务器启动应用: + ``` + gunicorn -w 4 -b 0.0.0.0:5000 "app:create_app()" + ``` +5. 配置反向代理,将 `https://infogenie.api.shumengya.top` 反向代理到后端服务 + +也可以参考 `backend/deploy.bat` 脚本中的部署说明。 + +### ⚙️ 配置说明 + +#### 前端配置 + +前端通过环境变量配置API基础URL: + +- 开发环境:`.env.development` 文件中设置 `REACT_APP_API_URL=http://localhost:5000` +- 生产环境:`.env.production` 文件中设置 `REACT_APP_API_URL=https://infogenie.api.shumengya.top` + +#### 后端配置 + +后端通过 `config.py` 和环境变量进行配置: + +- MongoDB连接:通过环境变量 `MONGO_URI` 设置 +- 邮件服务:通过环境变量 `MAIL_USERNAME` 和 `MAIL_PASSWORD` 设置 +- CORS配置:在 `app.py` 中配置允许的前端域名 + +#### 60sAPI配置 + +60sAPI模块的静态文件位于 `frontend/60sapi` 目录,通过后端的静态文件服务提供访问。 + +各API模块的接口地址已配置为 `https://infogenie.api.shumengya.top/api/60s`。 + #### 前端依赖 ```bash cd frontend/react-app diff --git a/backend/.env.production b/backend/.env.production new file mode 100644 index 00000000..15d521db --- /dev/null +++ b/backend/.env.production @@ -0,0 +1,14 @@ +# 生产环境配置 + +# MongoDB配置 +MONGO_URI=mongodb://用户名:密码@主机地址:端口/InfoGenie?authSource=admin + +# 邮件配置 +MAIL_USERNAME=your-email@qq.com +MAIL_PASSWORD=your-app-password + +# 应用密钥 +SECRET_KEY=infogenie-production-secret-key-2025 + +# 会话安全配置 +SESSION_COOKIE_SECURE=True \ No newline at end of file diff --git a/backend/app.py b/backend/app.py index 7a381449..24528ab0 100644 --- a/backend/app.py +++ b/backend/app.py @@ -31,7 +31,7 @@ def create_app(): # 加载配置 app.config.from_object(Config) - # 启用CORS跨域支持 + # 启用CORS跨域支持(允许所有源) CORS(app, supports_credentials=True) # 初始化MongoDB @@ -182,6 +182,4 @@ def create_app(): if __name__ == '__main__': app = create_app() print("🚀 启动 InfoGenie 后端服务...") - print("📡 API地址: http://localhost:5000") - print("📚 文档地址: http://localhost:5000/api/health") - app.run(debug=True, host='0.0.0.0', port=5000) + app.run(debug=True, host='0.0.0.0', port=5002) diff --git a/backend/deploy.bat b/backend/deploy.bat new file mode 100644 index 00000000..2492b542 --- /dev/null +++ b/backend/deploy.bat @@ -0,0 +1,27 @@ +@echo off +echo ===== 开始部署后端应用到生产环境 ===== + +cd /d "%~dp0" + +echo 1. 安装依赖... +pip install -r requirements.txt + +echo 2. 部署说明: +echo. +echo 请确保以下配置已完成: +echo 1. 在服务器上配置环境变量或创建 .env 文件,包含以下内容: +echo MONGO_URI=你的MongoDB连接字符串 +echo MAIL_USERNAME=你的邮箱地址 +echo MAIL_PASSWORD=你的邮箱授权码 +echo SECRET_KEY=你的应用密钥 +echo. +echo 3. 启动后端服务: +echo 在生产环境中,建议使用 Gunicorn 或 uWSGI 作为 WSGI 服务器 +echo 示例命令:gunicorn -w 4 -b 0.0.0.0:5000 "app:create_app()" +echo. +echo 4. 配置反向代理: +echo 将 https://infogenie.api.shumengya.top 反向代理到后端服务 +echo. +echo ===== 后端应用部署准备完成 ===== + +pause \ No newline at end of file diff --git a/backend/modules/api_60s.py b/backend/modules/api_60s.py index 9d839809..5b520bb6 100644 --- a/backend/modules/api_60s.py +++ b/backend/modules/api_60s.py @@ -73,10 +73,13 @@ def scan_directories(): except: title = module_name + # 根据环境获取基础URL + base_url = 'https://infogenie.api.shumengya.top' + apis.append({ 'title': title, 'description': f'{module_name}相关功能', - 'link': f'http://localhost:5000/60sapi/{category_name}/{module_name}/index.html', + 'link': f'{base_url}/60sapi/{category_name}/{module_name}/index.html', 'status': 'active', 'color': gradient_colors[i % len(gradient_colors)] }) diff --git a/build_frontend.bat b/build_frontend.bat new file mode 100644 index 00000000..66bfcc0c --- /dev/null +++ b/build_frontend.bat @@ -0,0 +1,6 @@ +@echo off +cd /d "e:\Python\InfoGenie\frontend\react-app" +npm run build + +npx serve -s build +pause \ No newline at end of file diff --git a/frontend/60sapi/热搜榜单/抖音热搜榜/js/script.js b/frontend/60sapi/热搜榜单/抖音热搜榜/js/script.js index 03931e5d..b8501eb8 100644 --- a/frontend/60sapi/热搜榜单/抖音热搜榜/js/script.js +++ b/frontend/60sapi/热搜榜单/抖音热搜榜/js/script.js @@ -1,5 +1,5 @@ // 本地后端API接口 -const LOCAL_API_BASE = 'http://localhost:5000/api/60s'; +const LOCAL_API_BASE = 'https://infogenie.api.shumengya.top/api/60s'; // API接口列表(备用) const API_ENDPOINTS = [ diff --git a/frontend/react-app/.env.production b/frontend/react-app/.env.production new file mode 100644 index 00000000..6a5f6cc6 --- /dev/null +++ b/frontend/react-app/.env.production @@ -0,0 +1,2 @@ +# 生产环境API配置 +REACT_APP_API_URL=https://infogenie.api.shumengya.top \ No newline at end of file diff --git a/frontend/react-app/package.json b/frontend/react-app/package.json index 7016236e..ad0b2ebc 100644 --- a/frontend/react-app/package.json +++ b/frontend/react-app/package.json @@ -44,6 +44,5 @@ "last 1 firefox version", "last 1 safari version" ] - }, - "proxy": "http://localhost:5000" + } } diff --git a/frontend/react-app/public/sw.js b/frontend/react-app/public/sw.js new file mode 100644 index 00000000..e1b7536e --- /dev/null +++ b/frontend/react-app/public/sw.js @@ -0,0 +1,48 @@ +// Service Worker for InfoGenie App +const CACHE_NAME = 'infogenie-cache-v1'; +const urlsToCache = [ + '/', + '/index.html', + '/manifest.json' +]; + +// 安装Service Worker +self.addEventListener('install', event => { + event.waitUntil( + caches.open(CACHE_NAME) + .then(cache => { + console.log('Opened cache'); + return cache.addAll(urlsToCache); + }) + ); +}); + +// 拦截请求并从缓存中响应 +self.addEventListener('fetch', event => { + event.respondWith( + caches.match(event.request) + .then(response => { + // 如果找到缓存的响应,则返回缓存 + if (response) { + return response; + } + return fetch(event.request); + }) + ); +}); + +// 更新Service Worker +self.addEventListener('activate', event => { + const cacheWhitelist = [CACHE_NAME]; + event.waitUntil( + caches.keys().then(cacheNames => { + return Promise.all( + cacheNames.map(cacheName => { + if (cacheWhitelist.indexOf(cacheName) === -1) { + return caches.delete(cacheName); + } + }) + ); + }) + ); +}); \ No newline at end of file diff --git a/frontend/react-app/src/index.js b/frontend/react-app/src/index.js index 882116ee..484c43d2 100644 --- a/frontend/react-app/src/index.js +++ b/frontend/react-app/src/index.js @@ -9,3 +9,16 @@ root.render( ); + +// 注册Service Worker +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/sw.js') + .then(registration => { + console.log('ServiceWorker registration successful with scope: ', registration.scope); + }) + .catch(error => { + console.error('ServiceWorker registration failed: ', error); + }); + }); +} diff --git a/frontend/react-app/src/pages/AiModelPage.js b/frontend/react-app/src/pages/AiModelPage.js index dd882eba..33dfd0f3 100644 --- a/frontend/react-app/src/pages/AiModelPage.js +++ b/frontend/react-app/src/pages/AiModelPage.js @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; import { FiCpu, FiUser, FiExternalLink, FiArrowLeft } from 'react-icons/fi'; import { useUser } from '../contexts/UserContext'; -import axios from 'axios'; +import api from '../utils/api'; const AiContainer = styled.div` min-height: calc(100vh - 140px); @@ -262,7 +262,7 @@ const AiModelPage = () => { const fetchApps = async () => { try { setLoadingApps(true); - const response = await axios.get('/api/aimodelapp/scan-directories'); + const response = await api.get('/api/aimodelapp/scan-directories'); if (response.data.success) { setApps(response.data.apps); } else { @@ -278,7 +278,8 @@ const AiModelPage = () => { const handleLaunchApp = (app) => { // 将相对路径转换为完整的服务器地址 - const fullLink = `http://localhost:5000${app.link}`; + const baseUrl = process.env.REACT_APP_API_URL || 'https://infogenie.api.shumengya.top'; + const fullLink = `${baseUrl}${app.link}`; setEmbeddedApp({ ...app, link: fullLink }); }; diff --git a/frontend/react-app/src/pages/Api60sPage.js b/frontend/react-app/src/pages/Api60sPage.js index 20df365a..85cf7daf 100644 --- a/frontend/react-app/src/pages/Api60sPage.js +++ b/frontend/react-app/src/pages/Api60sPage.js @@ -238,81 +238,26 @@ const Api60sPage = () => { // 从后端API获取目录结构 const scanDirectories = async () => { try { - const response = await fetch('http://localhost:5000/api/60s/scan-directories'); + // 使用环境变量中配置的API URL + const baseUrl = process.env.REACT_APP_API_URL || 'https://infogenie.api.shumengya.top'; + const apiUrl = `${baseUrl}/api/60s/scan-directories`; + console.log('正在请求API目录结构:', apiUrl); + const response = await fetch(apiUrl); if (response.ok) { const data = await response.json(); return data; } } catch (error) { - console.warn('无法从后端获取目录结构,使用前端扫描方式'); + console.warn('无法从后端获取目录结构:', error); } return null; }; - // 前端扫描方式(备用) - const frontendScan = async () => { - const categories = []; - - for (const [categoryName, config] of Object.entries(categoryConfig)) { - const apis = []; - - // 尝试访问已知的模块列表(只包含实际存在的模块) - const knownModules = { - '热搜榜单': ['抖音热搜榜'], - '日更资讯': [], - '实用功能': [], - '娱乐消遣': [] - }; + // 前端扫描方式已移除 - const moduleNames = knownModules[categoryName] || []; - - for (let i = 0; i < moduleNames.length; i++) { - const moduleName = moduleNames[i]; - try { - const indexPath = `/60sapi/${categoryName}/${moduleName}/index.html`; - const fullUrl = `http://localhost:5000${indexPath}`; - const response = await fetch(fullUrl, { method: 'HEAD' }); - - if (response.ok) { - // 获取页面标题 - const htmlResponse = await fetch(fullUrl); - const html = await htmlResponse.text(); - const titleMatch = html.match(/(.*?)<\/title>/i); - const title = titleMatch ? titleMatch[1].trim() : moduleName; - - apis.push({ - title, - description: `${moduleName}相关功能`, - link: fullUrl, - status: 'active', - color: gradientColors[i % gradientColors.length] - }); - } - } catch (error) { - // 忽略访问失败的模块 - } - } - - if (apis.length > 0) { - categories.push({ - title: categoryName, - icon: config.icon, - color: config.color, - apis - }); - } - } - - return categories; - }; - - // 首先尝试后端扫描,失败则使用前端扫描 + // 只使用后端扫描 const backendResult = await scanDirectories(); - if (backendResult && backendResult.success) { - return backendResult.categories || []; - } else { - return await frontendScan(); - } + return backendResult && backendResult.success ? backendResult.categories || [] : []; } catch (error) { console.error('扫描API模块时出错:', error); diff --git a/frontend/react-app/src/pages/SmallGamePage.js b/frontend/react-app/src/pages/SmallGamePage.js index 3c8e6bd5..047d1d93 100644 --- a/frontend/react-app/src/pages/SmallGamePage.js +++ b/frontend/react-app/src/pages/SmallGamePage.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import styled from 'styled-components'; import { FiGrid, FiPlay, FiExternalLink, FiArrowLeft } from 'react-icons/fi'; -import axios from 'axios'; +import api from '../utils/api'; const GameContainer = styled.div` min-height: calc(100vh - 140px); @@ -233,7 +233,7 @@ const SmallGamePage = () => { const fetchGames = async () => { try { setLoading(true); - const response = await axios.get('/api/smallgame/scan-directories'); + const response = await api.get('/api/smallgame/scan-directories'); if (response.data.success) { setGames(response.data.games); } else { @@ -249,7 +249,8 @@ const SmallGamePage = () => { const handlePlayGame = (game) => { // 将相对路径转换为完整的服务器地址 - const fullLink = `http://localhost:5000${game.link}`; + const baseUrl = process.env.REACT_APP_API_URL || 'https://infogenie.api.shumengya.top'; + const fullLink = `${baseUrl}${game.link}`; setEmbeddedGame({ ...game, link: fullLink }); }; diff --git a/frontend/react-app/src/utils/api.js b/frontend/react-app/src/utils/api.js index 9a033504..4e8810ef 100644 --- a/frontend/react-app/src/utils/api.js +++ b/frontend/react-app/src/utils/api.js @@ -3,7 +3,7 @@ import toast from 'react-hot-toast'; // 创建axios实例 const api = axios.create({ - baseURL: process.env.REACT_APP_API_URL || '/api', + baseURL: process.env.REACT_APP_API_URL || 'https://infogenie.api.shumengya.top', timeout: 10000, withCredentials: true, // 支持携带cookie headers: { @@ -11,6 +11,9 @@ const api = axios.create({ } }); +// 打印当前使用的API URL,便于调试 +console.log('API Base URL:', process.env.REACT_APP_API_URL || 'https://infogenie.api.shumengya.top'); + // 请求拦截器 api.interceptors.request.use( (config) => { @@ -44,63 +47,63 @@ api.interceptors.response.use( // 认证相关API export const authAPI = { // 发送验证码 - sendVerification: (data) => api.post('/auth/send-verification', data), + sendVerification: (data) => api.post('/api/auth/send-verification', data), // 验证验证码 - verifyCode: (data) => api.post('/auth/verify-code', data), + verifyCode: (data) => api.post('/api/auth/verify-code', data), // 登录 - login: (credentials) => api.post('/auth/login', credentials), + login: (credentials) => api.post('/api/auth/login', credentials), // 注册 - register: (userData) => api.post('/auth/register', userData), + register: (userData) => api.post('/api/auth/register', userData), // 登出 - logout: () => api.post('/auth/logout'), + logout: () => api.post('/api/auth/logout'), // 检查登录状态 - checkLogin: () => api.get('/auth/check'), + checkLogin: () => api.get('/api/auth/check'), }; // 用户相关API export const userAPI = { // 获取用户资料 - getProfile: () => api.get('/user/profile'), + getProfile: () => api.get('/api/user/profile'), // 修改密码 - changePassword: (passwordData) => api.post('/user/change-password', passwordData), + changePassword: (passwordData) => api.post('/api/user/change-password', passwordData), // 获取用户统计 - getStats: () => api.get('/user/stats'), + getStats: () => api.get('/api/user/stats'), // 删除账户 - deleteAccount: (password) => api.post('/user/delete', { password }), + deleteAccount: (password) => api.post('/api/user/delete', { password }), }; // 60s API相关接口 export const api60s = { // 抖音热搜 - getDouyinHot: () => api.get('/60s/douyin'), + getDouyinHot: () => api.get('/api/60s/douyin'), // 微博热搜 - getWeiboHot: () => api.get('/60s/weibo'), + getWeiboHot: () => api.get('/api/60s/weibo'), // 猫眼票房 - getMaoyanBoxOffice: () => api.get('/60s/maoyan'), + getMaoyanBoxOffice: () => api.get('/api/60s/maoyan'), // 60秒读懂世界 - get60sNews: () => api.get('/60s/60s'), + get60sNews: () => api.get('/api/60s/60s'), // 必应壁纸 - getBingWallpaper: () => api.get('/60s/bing-wallpaper'), + getBingWallpaper: () => api.get('/api/60s/bing-wallpaper'), // 天气信息 - getWeather: (city = '北京') => api.get(`/60s/weather?city=${encodeURIComponent(city)}`), + getWeather: (city = '北京') => api.get(`/api/60s/weather?city=${encodeURIComponent(city)}`), }; // 健康检查 export const healthAPI = { - check: () => api.get('/health'), + check: () => api.get('/api/health'), }; export default api; diff --git a/nginx-config-example.conf b/nginx-config-example.conf new file mode 100644 index 00000000..c0565ff5 --- /dev/null +++ b/nginx-config-example.conf @@ -0,0 +1,72 @@ +# Nginx配置示例 - InfoGenie部署 + +# 前端配置 - infogenie.shumengya.top +server { + listen 80; + server_name infogenie.shumengya.top; + + # 重定向HTTP到HTTPS + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + server_name infogenie.shumengya.top; + + # SSL证书配置 + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/key.pem; + + # 前端静态文件目录 + root /var/www/infogenie; + index index.html; + + # 处理React路由 + location / { + try_files $uri $uri/ /index.html; + } + + # 安全相关配置 + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options "nosniff"; +} + +# 后端配置 - infogenie.api.shumengya.top +server { + listen 80; + server_name infogenie.api.shumengya.top; + + # 重定向HTTP到HTTPS + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + server_name infogenie.api.shumengya.top; + + # SSL证书配置 + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/key.pem; + + # 反向代理到后端服务 + location / { + proxy_pass http://127.0.0.1:5000; + 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; + } + + # 安全相关配置 + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options "nosniff"; + + # 允许较大的上传文件 + client_max_body_size 10M; +} \ No newline at end of file