网页框架部署成功

This commit is contained in:
2025-09-04 16:03:09 +08:00
parent 71a648fdf4
commit 6f850caad1
16 changed files with 287 additions and 96 deletions

View File

@@ -6,6 +6,11 @@
InfoGenie 是一个前后端分离的多功能聚合应用提供实时数据接口、休闲游戏、AI工具等丰富功能。 InfoGenie 是一个前后端分离的多功能聚合应用提供实时数据接口、休闲游戏、AI工具等丰富功能。
### 🌐 部署环境
- **前端部署地址**: https://infogenie.shumengya.top
- **后端部署地址**: https://infogenie.api.shumengya.top
### 🏗️ 技术架构 ### 🏗️ 技术架构
- **前端**: React + Styled Components + React Router - **前端**: React + Styled Components + React Router
@@ -48,6 +53,60 @@ cd backend
pip install -r requirements.txt 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 ```bash
cd frontend/react-app cd frontend/react-app

14
backend/.env.production Normal file
View File

@@ -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

View File

@@ -31,7 +31,7 @@ def create_app():
# 加载配置 # 加载配置
app.config.from_object(Config) app.config.from_object(Config)
# 启用CORS跨域支持 # 启用CORS跨域支持(允许所有源)
CORS(app, supports_credentials=True) CORS(app, supports_credentials=True)
# 初始化MongoDB # 初始化MongoDB
@@ -182,6 +182,4 @@ def create_app():
if __name__ == '__main__': if __name__ == '__main__':
app = create_app() app = create_app()
print("🚀 启动 InfoGenie 后端服务...") print("🚀 启动 InfoGenie 后端服务...")
print("📡 API地址: http://localhost:5000") app.run(debug=True, host='0.0.0.0', port=5002)
print("📚 文档地址: http://localhost:5000/api/health")
app.run(debug=True, host='0.0.0.0', port=5000)

27
backend/deploy.bat Normal file
View File

@@ -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

View File

@@ -73,10 +73,13 @@ def scan_directories():
except: except:
title = module_name title = module_name
# 根据环境获取基础URL
base_url = 'https://infogenie.api.shumengya.top'
apis.append({ apis.append({
'title': title, 'title': title,
'description': f'{module_name}相关功能', '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', 'status': 'active',
'color': gradient_colors[i % len(gradient_colors)] 'color': gradient_colors[i % len(gradient_colors)]
}) })

6
build_frontend.bat Normal file
View File

@@ -0,0 +1,6 @@
@echo off
cd /d "e:\Python\InfoGenie\frontend\react-app"
npm run build
npx serve -s build
pause

View File

@@ -1,5 +1,5 @@
// 本地后端API接口 // 本地后端API接口
const LOCAL_API_BASE = 'http://localhost:5000/api/60s'; const LOCAL_API_BASE = 'https://infogenie.api.shumengya.top/api/60s';
// API接口列表备用 // API接口列表备用
const API_ENDPOINTS = [ const API_ENDPOINTS = [

View File

@@ -0,0 +1,2 @@
# 生产环境API配置
REACT_APP_API_URL=https://infogenie.api.shumengya.top

View File

@@ -44,6 +44,5 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
}, }
"proxy": "http://localhost:5000"
} }

48
frontend/react-app/public/sw.js vendored Normal file
View File

@@ -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);
}
})
);
})
);
});

View File

@@ -9,3 +9,16 @@ root.render(
<App /> <App />
</React.StrictMode> </React.StrictMode>
); );
// 注册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);
});
});
}

View File

@@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import { FiCpu, FiUser, FiExternalLink, FiArrowLeft } from 'react-icons/fi'; import { FiCpu, FiUser, FiExternalLink, FiArrowLeft } from 'react-icons/fi';
import { useUser } from '../contexts/UserContext'; import { useUser } from '../contexts/UserContext';
import axios from 'axios'; import api from '../utils/api';
const AiContainer = styled.div` const AiContainer = styled.div`
min-height: calc(100vh - 140px); min-height: calc(100vh - 140px);
@@ -262,7 +262,7 @@ const AiModelPage = () => {
const fetchApps = async () => { const fetchApps = async () => {
try { try {
setLoadingApps(true); setLoadingApps(true);
const response = await axios.get('/api/aimodelapp/scan-directories'); const response = await api.get('/api/aimodelapp/scan-directories');
if (response.data.success) { if (response.data.success) {
setApps(response.data.apps); setApps(response.data.apps);
} else { } else {
@@ -278,7 +278,8 @@ const AiModelPage = () => {
const handleLaunchApp = (app) => { 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 }); setEmbeddedApp({ ...app, link: fullLink });
}; };

View File

@@ -238,81 +238,26 @@ const Api60sPage = () => {
// 从后端API获取目录结构 // 从后端API获取目录结构
const scanDirectories = async () => { const scanDirectories = async () => {
try { 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) { if (response.ok) {
const data = await response.json(); const data = await response.json();
return data; return data;
} }
} catch (error) { } catch (error) {
console.warn('无法从后端获取目录结构,使用前端扫描方式'); console.warn('无法从后端获取目录结构:', error);
} }
return null; 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>(.*?)<\/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(); const backendResult = await scanDirectories();
if (backendResult && backendResult.success) { return backendResult && backendResult.success ? backendResult.categories || [] : [];
return backendResult.categories || [];
} else {
return await frontendScan();
}
} catch (error) { } catch (error) {
console.error('扫描API模块时出错:', error); console.error('扫描API模块时出错:', error);

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { FiGrid, FiPlay, FiExternalLink, FiArrowLeft } from 'react-icons/fi'; import { FiGrid, FiPlay, FiExternalLink, FiArrowLeft } from 'react-icons/fi';
import axios from 'axios'; import api from '../utils/api';
const GameContainer = styled.div` const GameContainer = styled.div`
min-height: calc(100vh - 140px); min-height: calc(100vh - 140px);
@@ -233,7 +233,7 @@ const SmallGamePage = () => {
const fetchGames = async () => { const fetchGames = async () => {
try { try {
setLoading(true); setLoading(true);
const response = await axios.get('/api/smallgame/scan-directories'); const response = await api.get('/api/smallgame/scan-directories');
if (response.data.success) { if (response.data.success) {
setGames(response.data.games); setGames(response.data.games);
} else { } else {
@@ -249,7 +249,8 @@ const SmallGamePage = () => {
const handlePlayGame = (game) => { 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 }); setEmbeddedGame({ ...game, link: fullLink });
}; };

View File

@@ -3,7 +3,7 @@ import toast from 'react-hot-toast';
// 创建axios实例 // 创建axios实例
const api = axios.create({ 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, timeout: 10000,
withCredentials: true, // 支持携带cookie withCredentials: true, // 支持携带cookie
headers: { 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( api.interceptors.request.use(
(config) => { (config) => {
@@ -44,63 +47,63 @@ api.interceptors.response.use(
// 认证相关API // 认证相关API
export const authAPI = { 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 // 用户相关API
export const userAPI = { 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相关接口 // 60s API相关接口
export const api60s = { 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秒读懂世界 // 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 = { export const healthAPI = {
check: () => api.get('/health'), check: () => api.get('/api/health'),
}; };
export default api; export default api;

72
nginx-config-example.conf Normal file
View File

@@ -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;
}