网页框架部署成功

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工具等丰富功能。
### 🌐 部署环境
- **前端部署地址**: 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

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)
# 启用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)

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:
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)]
})

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接口
const LOCAL_API_BASE = 'http://localhost:5000/api/60s';
const LOCAL_API_BASE = 'https://infogenie.api.shumengya.top/api/60s';
// API接口列表备用
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 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 />
</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 { 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 });
};

View File

@@ -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>(.*?)<\/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);

View File

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

View File

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

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