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