chore: sync local changes (2026-03-12)

This commit is contained in:
2026-03-12 18:58:26 +08:00
parent 04a4cb962a
commit 939442e061
348 changed files with 91638 additions and 92091 deletions

View File

@@ -1,36 +0,0 @@
# Node modules
InfoGenie-frontend/node_modules
InfoGenie-frontend/build
# Python cache
InfoGenie-backend/__pycache__
InfoGenie-backend/**/__pycache__
InfoGenie-backend/*.pyc
InfoGenie-backend/**/*.pyc
# Git
.git
.gitignore
# IDE
.vscode
.idea
*.swp
*.swo
# Logs
*.log
# OS
.DS_Store
Thumbs.db
# Test files
InfoGenie-backend/test
# Documentation
*.md
# Backup files
*.backup
*.bak

5
.gitignore vendored
View File

@@ -1,5 +0,0 @@
#项目自忽略
.vscode
InfoGenie-frontend/node_modules
InfoGenie-frontend/build
InfoGenie-backend/__pycache__

View File

@@ -1,57 +0,0 @@
# InfoGenie 统一 Docker 镜像
# 多阶段构建:前端构建 + 后端 + Nginx
# 阶段1: 前端构建
FROM node:18-alpine AS frontend-builder
WORKDIR /frontend
COPY InfoGenie-frontend/package*.json ./
RUN npm install --legacy-peer-deps
COPY InfoGenie-frontend/ ./
RUN npm run build
# 阶段2: 最终镜像
FROM python:3.10-slim
# 安装 Nginx 和必要的工具
RUN apt-get update && apt-get install -y \
nginx \
supervisor \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /app
# 复制后端代码
COPY InfoGenie-backend/ ./backend/
# 安装 Python 依赖
RUN pip install --no-cache-dir -r ./backend/requirements.txt gunicorn
# 复制前端构建产物到 Nginx 目录
COPY --from=frontend-builder /frontend/build /usr/share/nginx/html
# 创建持久化数据目录
RUN mkdir -p /app/data/logs
# 复制 Nginx 配置
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/default.conf /etc/nginx/conf.d/default.conf
# 复制 Supervisor 配置
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# 复制启动脚本
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# 暴露端口
EXPOSE 2323
# 设置环境变量
ENV FLASK_APP=app.py
ENV FLASK_ENV=production
ENV PYTHONUNBUFFERED=1
# 使用 supervisor 管理多进程
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -0,0 +1,19 @@
# Python cache
__pycache__
# Git
.git
.gitignore
# IDE
.vscode
.idea
*.swp
*.swo
# Test files
InfoGenie-backend/test

3
InfoGenie-backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
#项目自忽略
.vscode
__pycache__

View File

@@ -0,0 +1,30 @@
# InfoGenie 后端 Docker 镜像
# 仅包含后端服务,使用 Gunicorn
FROM python:3.10-slim
# 安装 curl 用于健康检查
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /app
# 复制依赖文件
COPY requirements.txt .
# 安装 Python 依赖
RUN pip install --no-cache-dir -r requirements.txt gunicorn
# 复制后端代码
COPY . .
# 创建持久化数据目录
RUN mkdir -p /app/data/logs
# 暴露端口
EXPOSE 2323
# 使用 Gunicorn 启动应用
CMD ["gunicorn", "--bind", "0.0.0.0:2323", "--workers", "4", "--threads", "2", "--timeout", "120", "--access-logfile", "-", "--error-logfile", "-", "app:app"]

View File

@@ -178,7 +178,9 @@ def create_app():
return app
# 为 Gunicorn 创建应用实例
app = create_app()
if __name__ == '__main__':
app = create_app()
print("🚀 启动 InfoGenie 后端服务...")
app.run(debug=True, host='0.0.0.0', port=5002)

View File

@@ -1,18 +1,18 @@
version: '3.8'
services:
infogenie:
infogenie-backend:
build:
context: .
dockerfile: Dockerfile
container_name: infogenie
container_name: infogenie-backend
restart: always
ports:
- "2323:2323"
volumes:
# 持久化数据映射
- /shumengya/docker/storage/infogenie/logs:/app/data/logs
- /shumengya/docker/storage/infogenie/data:/app/data
- /shumengya/docker/infogenie-backend/data:/app/data
environment:
# 从 .env 文件读取环境变量
- MONGO_URI=${MONGO_URI}
@@ -20,12 +20,15 @@ services:
- MAIL_PASSWORD=${MAIL_PASSWORD}
- SECRET_KEY=${SECRET_KEY}
- FLASK_ENV=production
# 生产环境配置
- HWT_DOMAIN=.shumengya.top
- HWT_SECURE=False # 如果使用 HTTPS 反向代理,设为 False直接 HTTPS 设为 True
env_file:
- ./InfoGenie-backend/.env
- .env
networks:
- infogenie-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:2323/api/health"]
test: ["CMD-SHELL", "curl -f http://localhost:2323/api/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3

View File

@@ -266,7 +266,7 @@ def get_qq_avatar_url(email):
return None
# 返回QQ头像API URL
return f"http://q1.qlogo.cn/g?b=qq&nk={qq_number}&s=100"
return f"https://q1.qlogo.cn/g?b=qq&nk={qq_number}&s=100"
# 清理过期验证码
def cleanup_expired_codes():

View File

@@ -1,34 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
测试注册邮件发送
"""
import requests
import json
def test_send_verification_email():
"""测试发送验证码邮件"""
url = "http://localhost:5000/api/auth/send-verification"
test_data = {
"email": "3205788256@qq.com", # 使用配置的邮箱
"type": "register"
}
try:
response = requests.post(url, json=test_data)
print(f"状态码: {response.status_code}")
print(f"响应: {response.json()}")
if response.status_code == 200:
print("\n✅ 邮件发送成功!请检查邮箱")
else:
print(f"\n❌ 邮件发送失败: {response.json().get('message', '未知错误')}")
except Exception as e:
print(f"❌ 请求失败: {str(e)}")
if __name__ == "__main__":
print("📧 测试注册邮件发送...")
test_send_verification_email()

View File

@@ -1,49 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
MongoDB连接测试
"""
from pymongo import MongoClient
def test_connection():
# 测试不同的连接配置
configs = [
"mongodb://shumengya:tyh%4019900420@192.168.1.233:27017/InfoGenie",
"mongodb://shumengya:tyh%4019900420@192.168.1.233:27017/InfoGenie?authSource=admin",
"mongodb://shumengya:tyh%4019900420@192.168.1.233:27017/InfoGenie?authSource=InfoGenie",
"mongodb://shumengya:tyh%4019900420@192.168.1.233:27017/?authSource=admin",
]
for i, uri in enumerate(configs):
print(f"\n测试配置 {i+1}: {uri}")
try:
client = MongoClient(uri, serverSelectionTimeoutMS=5000)
client.admin.command('ping')
print("✅ 连接成功!")
# 测试InfoGenie数据库
db = client.InfoGenie
collections = db.list_collection_names()
print(f"数据库集合: {collections}")
# 测试userdata集合
if 'userdata' in collections:
count = db.userdata.count_documents({})
print(f"userdata集合文档数: {count}")
client.close()
return uri
except Exception as e:
print(f"❌ 连接失败: {str(e)}")
return None
if __name__ == "__main__":
print("🔧 测试MongoDB连接...")
success_uri = test_connection()
if success_uri:
print(f"\n✅ 成功的连接字符串: {success_uri}")
else:
print("\n❌ 所有连接尝试都失败了")

View File

@@ -1,100 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
测试为指定账号增加萌芽币接口 (/api/user/add-coins)
"""
import os
import sys
import json
from datetime import datetime
# 加入后端根目录到路径导入create_app
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app import create_app
from modules.auth import generate_token
from werkzeug.security import generate_password_hash
def run_test():
"""运行加币接口测试,打印真实响应并断言结果"""
app = create_app()
with app.app_context():
db = app.mongo.db
users = db.userdata
# 构造一个临时测试用户(真实写库,测试结束删除)
test_email = "infogenie.test.addcoins@foxmail.com"
users.delete_many({'邮箱': test_email})
test_user = {
'邮箱': test_email,
'用户名': '测试用户_加币',
'密码': generate_password_hash('AddCoins123!'),
'头像': None,
'注册时间': datetime.now().isoformat(),
'最后登录': None,
'登录次数': 0,
'用户状态': 'active',
'等级': 0,
'经验': 0,
'萌芽币': 0,
'签到系统': {
'连续签到天数': 0,
'今日是否已签到': False,
'签到时间': datetime.now().strftime('%Y-%m-%d')
}
}
insert_result = users.insert_one(test_user)
test_user_id = str(insert_result.inserted_id)
# 生成有效JWT用于认证
token = generate_token({
'user_id': test_user_id,
'email': test_email,
'username': test_user['用户名']
})
client = app.test_client()
# 第一次加币: +500
resp1 = client.post(
'/api/user/add-coins',
headers={'Authorization': f'Bearer {token}'},
json={'email': test_email, 'amount': 500}
)
print('第一次加币 状态码:', resp1.status_code)
data1 = resp1.get_json()
print('第一次加币 响应:')
print(json.dumps(data1, ensure_ascii=False, indent=2))
assert resp1.status_code == 200
assert data1.get('success') is True
assert data1['data']['before_coins'] == 0
assert data1['data']['added'] == 500
assert data1['data']['new_coins'] == 500
# 第二次加币: +200
resp2 = client.post(
'/api/user/add-coins',
headers={'Authorization': f'Bearer {token}'},
json={'email': test_email, 'amount': 200}
)
print('第二次加币 状态码:', resp2.status_code)
data2 = resp2.get_json()
print('第二次加币 响应:')
print(json.dumps(data2, ensure_ascii=False, indent=2))
assert resp2.status_code == 200
assert data2.get('success') is True
assert data2['data']['before_coins'] == 500
assert data2['data']['added'] == 200
assert data2['data']['new_coins'] == 700
# 清理临时测试用户
users.delete_many({'邮箱': test_email})
if __name__ == '__main__':
print('🔧 开始测试 /api/user/add-coins 接口...')
run_test()
print('✅ 测试完成!')

View File

@@ -1,35 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
测试邮件发送功能
"""
import requests
import json
def test_send_verification():
"""测试发送验证码"""
url = "http://localhost:5000/api/auth/send-verification"
# 测试数据
test_data = {
"email": "3205788256@qq.com", # 使用配置中的测试邮箱
"type": "register"
}
try:
response = requests.post(url, json=test_data)
print(f"状态码: {response.status_code}")
print(f"响应内容: {response.json()}")
if response.status_code == 200:
print("✅ 邮件发送成功!")
else:
print("❌ 邮件发送失败")
except Exception as e:
print(f"❌ 请求失败: {str(e)}")
if __name__ == "__main__":
print("📧 测试邮件发送功能...")
test_send_verification()

View File

@@ -1,81 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试修复后的邮件发送功能
"""
import sys
import os
# 添加父目录到路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from modules.email_service import send_verification_email, verify_code
def test_email_sending():
"""
测试邮件发送功能
"""
print("=== 测试邮件发送功能 ===")
# 测试邮箱请替换为你的QQ邮箱
test_email = "3205788256@qq.com" # 替换为实际的测试邮箱
print(f"正在向 {test_email} 发送注册验证码...")
# 发送注册验证码
result = send_verification_email(test_email, 'register')
print(f"发送结果: {result}")
if result['success']:
print("✅ 邮件发送成功!")
if 'code' in result:
print(f"验证码: {result['code']}")
# 测试验证码验证
print("\n=== 测试验证码验证 ===")
verify_result = verify_code(test_email, result['code'])
print(f"验证结果: {verify_result}")
if verify_result['success']:
print("✅ 验证码验证成功!")
else:
print("❌ 验证码验证失败!")
else:
print("❌ 邮件发送失败!")
print(f"错误信息: {result['message']}")
def test_login_email():
"""
测试登录验证码邮件
"""
print("\n=== 测试登录验证码邮件 ===")
test_email = "3205788256@qq.com" # 替换为实际的测试邮箱
print(f"正在向 {test_email} 发送登录验证码...")
result = send_verification_email(test_email, 'login')
print(f"发送结果: {result}")
if result['success']:
print("✅ 登录验证码邮件发送成功!")
if 'code' in result:
print(f"验证码: {result['code']}")
else:
print("❌ 登录验证码邮件发送失败!")
print(f"错误信息: {result['message']}")
if __name__ == '__main__':
print("InfoGenie 邮件服务测试")
print("=" * 50)
# 测试注册验证码
test_email_sending()
# 测试登录验证码
test_login_email()
print("\n测试完成!")

View File

@@ -1,70 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
测试MongoDB连接
"""
import os
from pymongo import MongoClient
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
def test_mongodb_connection():
"""测试MongoDB连接"""
try:
# 获取连接字符串
mongo_uri = os.environ.get('MONGO_URI')
print(f"连接字符串: {mongo_uri}")
# 创建连接
client = MongoClient(mongo_uri)
# 测试连接
client.admin.command('ping')
print("✅ MongoDB连接成功")
# 获取数据库
db = client.InfoGenie
print(f"数据库: {db.name}")
# 测试集合访问
userdata_collection = db.userdata
print(f"用户集合: {userdata_collection.name}")
# 测试查询(计算文档数量)
count = userdata_collection.count_documents({})
print(f"用户数据集合中有 {count} 个文档")
# 关闭连接
client.close()
except Exception as e:
print(f"❌ MongoDB连接失败: {str(e)}")
# 尝试其他认证数据库
print("\n尝试使用不同的认证配置...")
try:
# 尝试不指定认证数据库
uri_without_auth = "mongodb://shumengya:tyh%4019900420@192.168.1.233:27017/InfoGenie"
client2 = MongoClient(uri_without_auth)
client2.admin.command('ping')
print("✅ 不使用authSource连接成功")
client2.close()
except Exception as e2:
print(f"❌ 无authSource也失败: {str(e2)}")
# 尝试使用InfoGenie作为认证数据库
try:
uri_with_infogenie_auth = "mongodb://shumengya:tyh%4019900420@192.168.1.233:27017/InfoGenie?authSource=InfoGenie"
client3 = MongoClient(uri_with_infogenie_auth)
client3.admin.command('ping')
print("✅ 使用InfoGenie作为authSource连接成功")
client3.close()
except Exception as e3:
print(f"❌ InfoGenie authSource也失败: {str(e3)}")
if __name__ == "__main__":
print("🔧 测试MongoDB连接...")
test_mongodb_connection()

View File

@@ -1,81 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
测试列出所有用户的HTTP接口 (/api/user/list)
"""
import os
import sys
import json
from datetime import datetime
# 将后端根目录加入路径便于导入app
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app import create_app
from modules.auth import generate_token
from werkzeug.security import generate_password_hash
def run_test():
"""运行用户列表接口测试,输出真实数据"""
# 使用.env中的真实Mongo配置不造假
app = create_app()
with app.app_context():
db = app.mongo.db
users = db.userdata
# 插入一个测试用户(真实写入后再删除),确保可验证接口输出
test_email = "infogenie.test.user@foxmail.com"
users.delete_many({'邮箱': test_email})
test_user = {
'邮箱': test_email,
'用户名': '测试用户_列表',
'密码': generate_password_hash('TestPass123!'),
'头像': None,
'注册时间': datetime.now().isoformat(),
'最后登录': None,
'登录次数': 0,
'用户状态': 'active',
'等级': 0,
'经验': 0,
'萌芽币': 0,
'签到系统': {
'连续签到天数': 0,
'今日是否已签到': False,
'签到时间': datetime.now().strftime('%Y-%m-%d')
}
}
insert_result = users.insert_one(test_user)
test_user_id = str(insert_result.inserted_id)
# 生成有效JWT满足认证要求
token = generate_token({
'user_id': test_user_id,
'email': test_email,
'username': test_user['用户名']
})
client = app.test_client()
resp = client.get('/api/user/list', headers={'Authorization': f'Bearer {token}'})
print("状态码:", resp.status_code)
data = resp.get_json()
print("响应内容:")
print(json.dumps(data, ensure_ascii=False, indent=2))
# 基本断言,确保返回真实列表数据且包含刚插入的测试用户
assert resp.status_code == 200
assert data.get('success') is True
assert isinstance(data.get('data'), list)
assert any(u.get('email') == test_email for u in data['data'])
# 清理测试数据
users.delete_many({'邮箱': test_email})
if __name__ == '__main__':
print('🔎 开始测试 /api/user/list 接口...')
run_test()
print('✅ 测试完成!')

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

View File

@@ -7,6 +7,15 @@
<meta name="description" content="✨ 万象口袋 - 一个多功能的聚合软件应用提供聚合应用、小游戏、AI模型工具等丰富功能" />
<meta name="keywords" content="聚合应用,热搜榜单,小游戏,AI模型,实时资讯,工具集合" />
<meta name="author" content="万象口袋" />
<meta name="application-name" content="万象口袋" />
<!-- PWA / App meta -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="万象口袋" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="msapplication-TileColor" content="#667eea" />
<meta name="msapplication-TileImage" content="%PUBLIC_URL%/icons/icon-192.png" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
@@ -19,10 +28,11 @@
<meta property="twitter:description" content="🎨一个跨平台的多功能聚合软件应用" />
<!-- Favicon -->
<link rel="icon" href="%PUBLIC_URL%/assets/logo.png" />
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/icons/favicon-32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/icons/favicon-16.png" />
<!-- Apple Touch Icon -->
<link rel="apple-touch-icon" href="%PUBLIC_URL%/assets/logo.png" />
<link rel="apple-touch-icon" sizes="180x180" href="%PUBLIC_URL%/icons/apple-touch-icon.png" />
<!-- Manifest -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
@@ -352,19 +362,6 @@
window.addEventListener('error', function(e) {
console.error('应用加载错误:', e.error);
});
// Service Worker 注册
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js')
.then(function(registration) {
console.log('SW registered: ', registration);
})
.catch(function(registrationError) {
console.log('SW registration failed: ', registrationError);
});
});
}
</script>
</body>
</html>

View File

@@ -4,27 +4,34 @@
"description": "🎨 一个多功能的聚合软件应用提供聚合应用、休闲小游戏、AI模型工具等丰富功能",
"icons": [
{
"src": "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>✨</text></svg>",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/svg+xml"
},
{
"src": "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>✨</text></svg>",
"type": "image/svg+xml",
"src": "/icons/icon-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>✨</text></svg>",
"type": "image/svg+xml",
"src": "/icons/icon-512.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "/icons/icon-192-maskable.png",
"type": "image/png",
"sizes": "192x192",
"purpose": "maskable"
},
{
"src": "/icons/icon-512-maskable.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "maskable"
}
],
"start_url": "/",
"start_url": ".",
"display": "standalone",
"theme_color": "#667eea",
"background_color": "#ffffff",
"orientation": "portrait-primary",
"scope": "/",
"scope": ".",
"lang": "zh-CN",
"categories": ["utilities", "productivity", "entertainment"],
"screenshots": [

View File

@@ -1,48 +1,150 @@
// Service Worker for InfoGenie App
const CACHE_NAME = 'infogenie-cache-v1';
const urlsToCache = [
// Service Worker for InfoGenie App (PWA)
// 注意PWA 必须在 HTTPS或 localhost下才能生效
const CACHE_VERSION = 'v2';
const PRECACHE_NAME = `infogenie-precache-${CACHE_VERSION}`;
const RUNTIME_NAME = `infogenie-runtime-${CACHE_VERSION}`;
const CORE_ASSETS = [
'/',
'/index.html',
'/manifest.json'
'/manifest.json',
'/icons/icon-192.png',
'/icons/icon-512.png',
'/icons/icon-192-maskable.png',
'/icons/icon-512-maskable.png',
'/icons/apple-touch-icon.png',
'/icons/favicon-32.png',
'/icons/favicon-16.png'
];
// 安装Service Worker
async function safeAddAll(cache, urls) {
const uniqueUrls = Array.from(new Set(urls)).filter(Boolean);
const results = await Promise.allSettled(uniqueUrls.map(url => cache.add(url)));
const failures = results.filter(r => r.status === 'rejected');
if (failures.length > 0) {
console.warn('[SW] Some assets failed to cache:', failures.length);
}
}
async function precacheEntrypoints(cache) {
try {
const res = await fetch('/asset-manifest.json', { cache: 'no-store' });
if (!res.ok) return;
const manifest = await res.json();
const filesObj = manifest && typeof manifest === 'object' ? manifest.files : undefined;
const files = filesObj && typeof filesObj === 'object' ? Object.values(filesObj) : [];
const entrypoints = Array.isArray(manifest.entrypoints) ? manifest.entrypoints : [];
const urls = [...files, ...entrypoints]
.filter(p => typeof p === 'string')
.map(p => (p.startsWith('/') ? p : `/${p}`))
.filter(p => !p.endsWith('.map'));
await safeAddAll(cache, urls);
} catch (err) {
console.warn('[SW] Failed to precache entrypoints:', err);
}
}
self.addEventListener('install', event => {
self.skipWaiting();
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
(async () => {
const cache = await caches.open(PRECACHE_NAME);
await safeAddAll(cache, CORE_ASSETS);
await precacheEntrypoints(cache);
})()
);
});
// 拦截请求并从缓存中响应
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);
(async () => {
const cacheNames = await caches.keys();
await Promise.all(
cacheNames.map(name => {
if (name !== PRECACHE_NAME && name !== RUNTIME_NAME) {
return caches.delete(name);
}
return undefined;
})
);
})
await self.clients.claim();
})()
);
});
function isNavigationRequest(request) {
return request.mode === 'navigate' || request.destination === 'document';
}
function shouldHandleRequest(url, request) {
if (request.method !== 'GET') return false;
if (url.origin !== self.location.origin) return false;
return true;
}
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (!shouldHandleRequest(url, event.request)) return;
// 不缓存后端 API如需缓存请在这里加规则
if (url.pathname.startsWith('/api')) return;
// 页面请求:优先网络,离线回退到缓存
if (isNavigationRequest(event.request)) {
event.respondWith(
(async () => {
try {
const networkResponse = await fetch(event.request);
const cache = await caches.open(RUNTIME_NAME);
cache.put(event.request, networkResponse.clone());
return networkResponse;
} catch (err) {
const cached = await caches.match(event.request);
return cached || caches.match('/index.html');
}
})()
);
return;
}
// 静态资源stale-while-revalidate
event.respondWith(
(async () => {
const cached = await caches.match(event.request);
const cache = await caches.open(RUNTIME_NAME);
const networkFetch = (async () => {
try {
const response = await fetch(event.request);
if (response && response.ok) {
cache.put(event.request, response.clone());
}
return response;
} catch (err) {
return undefined;
}
})();
if (cached) {
event.waitUntil(networkFetch);
return cached;
}
const response = await networkFetch;
if (response) return response;
return new Response('', { status: 504, statusText: 'Offline' });
})()
);
});
self.addEventListener('message', event => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});

View File

@@ -122,7 +122,7 @@ export const UserProvider = ({ children }) => {
if (qqDomains.includes(domain)) {
const qqNumber = email.split('@')[0];
if (/^\d+$/.test(qqNumber)) {
return `http://q1.qlogo.cn/g?b=qq&nk=${qqNumber}&s=100`;
return `https://q1.qlogo.cn/g?b=qq&nk=${qqNumber}&s=100`;
}
}

View File

@@ -11,9 +11,13 @@ root.render(
);
// 注册Service Worker
if ('serviceWorker' in navigator) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
const publicUrl = (process.env.PUBLIC_URL || '').replace(/\/$/, '');
const swUrl = `${publicUrl}/sw.js`;
navigator.serviceWorker
.register(swUrl)
.then(registration => {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
})
@@ -21,4 +25,8 @@ if ('serviceWorker' in navigator) {
console.error('ServiceWorker registration failed: ', error);
});
});
} else if (process.env.NODE_ENV !== 'production' && 'serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(registrations => {
registrations.forEach(registration => registration.unregister());
});
}

View File

@@ -363,7 +363,7 @@ const UserProfilePage = () => {
if (qqDomains.includes(domain)) {
const qqNumber = email.split('@')[0];
if (/^\d+$/.test(qqNumber)) {
return `http://q1.qlogo.cn/g?b=qq&nk=${qqNumber}&s=100`;
return `https://q1.qlogo.cn/g?b=qq&nk=${qqNumber}&s=100`;
}
}

View File

@@ -1,74 +0,0 @@
server {
listen 2323;
server_name _;
# 前端静态文件根目录
root /usr/share/nginx/html;
index index.html;
# 日志配置
access_log /app/data/logs/access.log;
error_log /app/data/logs/error.log;
# API 请求转发到后端
location /api/ {
proxy_pass http://127.0.0.1:5002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
proxy_connect_timeout 300s;
}
# 60sapi 静态文件路由
location /60sapi/ {
alias /usr/share/nginx/html/60sapi/;
try_files $uri $uri/ =404;
add_header Cache-Control "public, max-age=3600";
}
# smallgame 静态文件路由
location /smallgame/ {
alias /usr/share/nginx/html/smallgame/;
try_files $uri $uri/ =404;
add_header Cache-Control "public, max-age=3600";
}
# aimodelapp 静态文件路由
location /aimodelapp/ {
alias /usr/share/nginx/html/aimodelapp/;
try_files $uri $uri/ =404;
add_header Cache-Control "public, max-age=3600";
}
# toolbox 静态文件路由
location /toolbox/ {
alias /usr/share/nginx/html/toolbox/;
try_files $uri $uri/ =404;
add_header Cache-Control "public, max-age=3600";
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# React Router 支持 - 所有其他请求返回 index.html
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# 健康检查
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}

View File

@@ -1,26 +0,0 @@
#!/bin/bash
set -e
echo "🚀 启动 InfoGenie 服务..."
# 创建必要的目录
mkdir -p /app/data/logs
# 检查环境变量
if [ -z "$MONGO_URI" ]; then
echo "⚠️ 警告: MONGO_URI 未设置"
fi
if [ -z "$MAIL_USERNAME" ]; then
echo "⚠️ 警告: MAIL_USERNAME 未设置"
fi
echo "✅ 环境变量检查完成"
# 测试 Nginx 配置
echo "🔍 检查 Nginx 配置..."
nginx -t
# 启动 Supervisor
echo "🎯 启动服务进程..."
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf

View File

@@ -1,41 +0,0 @@
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 1024;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式
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 /app/data/logs/nginx_access.log main;
error_log /app/data/logs/nginx_error.log warn;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 20M;
# Gzip 压缩
gzip on;
gzip_vary on;
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/rss+xml font/truetype font/opentype
application/vnd.ms-fontobject image/svg+xml;
# 包含站点配置
include /etc/nginx/conf.d/*.conf;
}

View File

@@ -1,24 +0,0 @@
[supervisord]
nodaemon=true
user=root
logfile=/app/data/logs/supervisord.log
pidfile=/var/run/supervisord.pid
[program:gunicorn]
directory=/app/backend
command=gunicorn -w 4 -b 127.0.0.1:5002 --timeout 300 --access-logfile /app/data/logs/gunicorn_access.log --error-logfile /app/data/logs/gunicorn_error.log "app:create_app()"
autostart=true
autorestart=true
startretries=3
stderr_logfile=/app/data/logs/gunicorn_err.log
stdout_logfile=/app/data/logs/gunicorn_out.log
priority=1
[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
startretries=3
stderr_logfile=/app/data/logs/nginx_err.log
stdout_logfile=/app/data/logs/nginx_out.log
priority=2

89
nginx-frontend.conf Normal file
View File

@@ -0,0 +1,89 @@
# InfoGenie 前端 Nginx 配置
# 部署在 192.168.1.100:1313
# 域名: infogenie.shumengya.top
server {
listen 1313;
listen [::]:1313;
server_name infogenie.shumengya.top 192.168.1.100;
# 网站根目录
root /var/www/infogenie;
index index.html;
# 日志配置
access_log /var/log/nginx/infogenie-access.log;
error_log /var/log/nginx/infogenie-error.log;
# Gzip 压缩
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/javascript application/xml+rss
application/json application/xml;
# 前端 SPA 路由支持
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache";
}
# 静态资源缓存策略
location ~* \.(jpg|jpeg|png|gif|ico|svg)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
location ~* \.(css|js)$ {
expires 7d;
add_header Cache-Control "public";
}
location ~* \.(woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 可选:如果需要代理后端 API
# 这样前端可以使用相对路径 /api 访问后端
location /api {
proxy_pass http://192.168.1.233:2323;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
# CORS 配置
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header Access-Control-Expose-Headers 'Content-Length,Content-Range' always;
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header Access-Control-Max-Age 1728000;
add_header Content-Type 'text/plain; charset=utf-8';
add_header Content-Length 0;
return 204;
}
}
# 禁止访问隐藏文件
location ~ /\. {
deny all;
}
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}

0
万象口袋 Normal file
View File