chore: sync local changes (2026-03-12)
@@ -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
@@ -1,5 +0,0 @@
|
|||||||
#项目自忽略
|
|
||||||
.vscode
|
|
||||||
InfoGenie-frontend/node_modules
|
|
||||||
InfoGenie-frontend/build
|
|
||||||
InfoGenie-backend/__pycache__
|
|
||||||
57
Dockerfile
@@ -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"]
|
|
||||||
19
InfoGenie-backend/.dockerignore
Normal 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
@@ -0,0 +1,3 @@
|
|||||||
|
#项目自忽略
|
||||||
|
.vscode
|
||||||
|
__pycache__
|
||||||
30
InfoGenie-backend/Dockerfile
Normal 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"]
|
||||||
@@ -178,7 +178,9 @@ def create_app():
|
|||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
# 为 Gunicorn 创建应用实例
|
||||||
|
app = create_app()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = create_app()
|
|
||||||
print("🚀 启动 InfoGenie 后端服务...")
|
print("🚀 启动 InfoGenie 后端服务...")
|
||||||
app.run(debug=True, host='0.0.0.0', port=5002)
|
app.run(debug=True, host='0.0.0.0', port=5002)
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
infogenie:
|
infogenie-backend:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: infogenie
|
container_name: infogenie-backend
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "2323:2323"
|
- "2323:2323"
|
||||||
volumes:
|
volumes:
|
||||||
# 持久化数据映射
|
# 持久化数据映射
|
||||||
- /shumengya/docker/storage/infogenie/logs:/app/data/logs
|
- /shumengya/docker/infogenie-backend/data:/app/data
|
||||||
- /shumengya/docker/storage/infogenie/data:/app/data
|
|
||||||
environment:
|
environment:
|
||||||
# 从 .env 文件读取环境变量
|
# 从 .env 文件读取环境变量
|
||||||
- MONGO_URI=${MONGO_URI}
|
- MONGO_URI=${MONGO_URI}
|
||||||
@@ -20,12 +20,15 @@ services:
|
|||||||
- MAIL_PASSWORD=${MAIL_PASSWORD}
|
- MAIL_PASSWORD=${MAIL_PASSWORD}
|
||||||
- SECRET_KEY=${SECRET_KEY}
|
- SECRET_KEY=${SECRET_KEY}
|
||||||
- FLASK_ENV=production
|
- FLASK_ENV=production
|
||||||
|
# 生产环境配置
|
||||||
|
- HWT_DOMAIN=.shumengya.top
|
||||||
|
- HWT_SECURE=False # 如果使用 HTTPS 反向代理,设为 False;直接 HTTPS 设为 True
|
||||||
env_file:
|
env_file:
|
||||||
- ./InfoGenie-backend/.env
|
- .env
|
||||||
networks:
|
networks:
|
||||||
- infogenie-network
|
- infogenie-network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:2323/api/health"]
|
test: ["CMD-SHELL", "curl -f http://localhost:2323/api/health || exit 1"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
@@ -266,7 +266,7 @@ def get_qq_avatar_url(email):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# 返回QQ头像API URL
|
# 返回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():
|
def cleanup_expired_codes():
|
||||||
|
|||||||
@@ -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()
|
|
||||||
@@ -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❌ 所有连接尝试都失败了")
|
|
||||||
@@ -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('✅ 测试完成!')
|
|
||||||
@@ -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()
|
|
||||||
@@ -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测试完成!")
|
|
||||||
@@ -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()
|
|
||||||
@@ -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('✅ 测试完成!')
|
|
||||||
BIN
InfoGenie-frontend/public/icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
InfoGenie-frontend/public/icons/favicon-16.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
InfoGenie-frontend/public/icons/favicon-32.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
InfoGenie-frontend/public/icons/icon-192-maskable.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
InfoGenie-frontend/public/icons/icon-192.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
InfoGenie-frontend/public/icons/icon-512-maskable.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
InfoGenie-frontend/public/icons/icon-512.png
Normal file
|
After Width: | Height: | Size: 256 KiB |
@@ -7,6 +7,15 @@
|
|||||||
<meta name="description" content="✨ 万象口袋 - 一个多功能的聚合软件应用,提供聚合应用、小游戏、AI模型工具等丰富功能" />
|
<meta name="description" content="✨ 万象口袋 - 一个多功能的聚合软件应用,提供聚合应用、小游戏、AI模型工具等丰富功能" />
|
||||||
<meta name="keywords" content="聚合应用,热搜榜单,小游戏,AI模型,实时资讯,工具集合" />
|
<meta name="keywords" content="聚合应用,热搜榜单,小游戏,AI模型,实时资讯,工具集合" />
|
||||||
<meta name="author" content="万象口袋" />
|
<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 -->
|
<!-- Open Graph / Facebook -->
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
@@ -19,10 +28,11 @@
|
|||||||
<meta property="twitter:description" content="🎨一个跨平台的多功能聚合软件应用" />
|
<meta property="twitter:description" content="🎨一个跨平台的多功能聚合软件应用" />
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- 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 -->
|
<!-- 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 -->
|
<!-- Manifest -->
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
@@ -352,19 +362,6 @@
|
|||||||
window.addEventListener('error', function(e) {
|
window.addEventListener('error', function(e) {
|
||||||
console.error('应用加载错误:', e.error);
|
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -4,27 +4,34 @@
|
|||||||
"description": "🎨 一个多功能的聚合软件应用,提供聚合应用、休闲小游戏、AI模型工具等丰富功能",
|
"description": "🎨 一个多功能的聚合软件应用,提供聚合应用、休闲小游戏、AI模型工具等丰富功能",
|
||||||
"icons": [
|
"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>",
|
"src": "/icons/icon-192.png",
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
"type": "image/png",
|
||||||
"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",
|
|
||||||
"sizes": "192x192"
|
"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>",
|
"src": "/icons/icon-512.png",
|
||||||
"type": "image/svg+xml",
|
"type": "image/png",
|
||||||
"sizes": "512x512"
|
"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",
|
"display": "standalone",
|
||||||
"theme_color": "#667eea",
|
"theme_color": "#667eea",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#ffffff",
|
||||||
"orientation": "portrait-primary",
|
"orientation": "portrait-primary",
|
||||||
"scope": "/",
|
"scope": ".",
|
||||||
"lang": "zh-CN",
|
"lang": "zh-CN",
|
||||||
"categories": ["utilities", "productivity", "entertainment"],
|
"categories": ["utilities", "productivity", "entertainment"],
|
||||||
"screenshots": [
|
"screenshots": [
|
||||||
|
|||||||
@@ -1,48 +1,150 @@
|
|||||||
// Service Worker for InfoGenie App
|
// Service Worker for InfoGenie App (PWA)
|
||||||
const CACHE_NAME = 'infogenie-cache-v1';
|
// 注意:PWA 必须在 HTTPS(或 localhost)下才能生效
|
||||||
const urlsToCache = [
|
|
||||||
|
const CACHE_VERSION = 'v2';
|
||||||
|
const PRECACHE_NAME = `infogenie-precache-${CACHE_VERSION}`;
|
||||||
|
const RUNTIME_NAME = `infogenie-runtime-${CACHE_VERSION}`;
|
||||||
|
|
||||||
|
const CORE_ASSETS = [
|
||||||
'/',
|
'/',
|
||||||
'/index.html',
|
'/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.addEventListener('install', event => {
|
||||||
|
self.skipWaiting();
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches.open(CACHE_NAME)
|
(async () => {
|
||||||
.then(cache => {
|
const cache = await caches.open(PRECACHE_NAME);
|
||||||
console.log('Opened cache');
|
await safeAddAll(cache, CORE_ASSETS);
|
||||||
return cache.addAll(urlsToCache);
|
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 => {
|
self.addEventListener('activate', event => {
|
||||||
const cacheWhitelist = [CACHE_NAME];
|
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches.keys().then(cacheNames => {
|
(async () => {
|
||||||
return Promise.all(
|
const cacheNames = await caches.keys();
|
||||||
cacheNames.map(cacheName => {
|
await Promise.all(
|
||||||
if (cacheWhitelist.indexOf(cacheName) === -1) {
|
cacheNames.map(name => {
|
||||||
return caches.delete(cacheName);
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export const UserProvider = ({ children }) => {
|
|||||||
if (qqDomains.includes(domain)) {
|
if (qqDomains.includes(domain)) {
|
||||||
const qqNumber = email.split('@')[0];
|
const qqNumber = email.split('@')[0];
|
||||||
if (/^\d+$/.test(qqNumber)) {
|
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`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,13 @@ root.render(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 注册Service Worker
|
// 注册Service Worker
|
||||||
if ('serviceWorker' in navigator) {
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
window.addEventListener('load', () => {
|
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 => {
|
.then(registration => {
|
||||||
console.log('ServiceWorker registration successful with scope: ', registration.scope);
|
console.log('ServiceWorker registration successful with scope: ', registration.scope);
|
||||||
})
|
})
|
||||||
@@ -21,4 +25,8 @@ if ('serviceWorker' in navigator) {
|
|||||||
console.error('ServiceWorker registration failed: ', error);
|
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());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ const UserProfilePage = () => {
|
|||||||
if (qqDomains.includes(domain)) {
|
if (qqDomains.includes(domain)) {
|
||||||
const qqNumber = email.split('@')[0];
|
const qqNumber = email.split('@')[0];
|
||||||
if (/^\d+$/.test(qqNumber)) {
|
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`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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;
|
||||||
|
}
|
||||||