258 lines
9.4 KiB
Python
258 lines
9.4 KiB
Python
from flask import Flask, jsonify, send_from_directory, request
|
||
from flask_cors import CORS
|
||
import json
|
||
import os
|
||
import random
|
||
|
||
# 检测运行模式:通过环境变量控制
|
||
RUN_MODE = os.environ.get('RUN_MODE', 'development') # development 或 production
|
||
|
||
# 数据文件路径 - 支持环境变量配置(需要先定义,因为后面会用到)
|
||
DATA_DIR = os.environ.get('DATA_DIR', os.path.join(os.path.dirname(__file__), 'data'))
|
||
|
||
# 根据运行模式配置
|
||
# 检查是否有前端构建文件(前后端分离时可能没有)
|
||
FRONTEND_BUILD_PATH = os.path.join(os.path.dirname(__file__), '..', 'frontend', 'build')
|
||
HAS_FRONTEND_BUILD = os.path.exists(FRONTEND_BUILD_PATH) and os.path.isdir(FRONTEND_BUILD_PATH)
|
||
|
||
# 背景图片目录 - 固定使用数据目录中的 background 文件夹
|
||
# 支持通过环境变量配置,默认在数据目录中
|
||
BACKGROUND_DIR = os.environ.get('BACKGROUND_DIR', os.path.join(DATA_DIR, 'background'))
|
||
|
||
if RUN_MODE == 'production' and HAS_FRONTEND_BUILD:
|
||
# 生产环境:使用构建后的前端(如果存在)
|
||
app = Flask(__name__, static_folder=FRONTEND_BUILD_PATH, static_url_path='')
|
||
else:
|
||
# 开发环境或纯后端模式:只提供 API
|
||
app = Flask(__name__)
|
||
|
||
CORS(app) # 允许跨域请求
|
||
|
||
def load_json_file(filename):
|
||
"""加载JSON文件"""
|
||
try:
|
||
with open(os.path.join(DATA_DIR, filename), 'r', encoding='utf-8') as f:
|
||
return json.load(f)
|
||
except FileNotFoundError:
|
||
return None
|
||
except Exception as e:
|
||
print(f"Error loading {filename}: {e}")
|
||
return None
|
||
|
||
@app.route('/api/profile', methods=['GET'])
|
||
def get_profile():
|
||
"""获取个人基本信息"""
|
||
data = load_json_file('profile.json')
|
||
if data:
|
||
return jsonify(data)
|
||
return jsonify({"error": "Profile没有找到"}), 404
|
||
|
||
@app.route('/api/projects', methods=['GET'])
|
||
def get_projects():
|
||
"""获取全部项目列表"""
|
||
data = load_json_file('projects.json')
|
||
if data:
|
||
return jsonify(data)
|
||
return jsonify({"error": "Projects没有找到"}), 404
|
||
|
||
@app.route('/api/contacts', methods=['GET'])
|
||
def get_contacts():
|
||
"""获取联系方式"""
|
||
data = load_json_file('contacts.json')
|
||
if data:
|
||
return jsonify(data)
|
||
return jsonify({"error": "Contacts没有找到"}), 404
|
||
|
||
@app.route('/api/techstack', methods=['GET'])
|
||
def get_techstack():
|
||
"""获取技术栈"""
|
||
data = load_json_file('techstack.json')
|
||
if data:
|
||
return jsonify(data)
|
||
return jsonify({"error": "Tech stack没有找到"}), 404
|
||
|
||
@app.route('/api/logo/<filename>', methods=['GET'])
|
||
def get_logo(filename):
|
||
"""提供技术栈图标文件"""
|
||
logo_dir = os.path.join(DATA_DIR, 'logo')
|
||
try:
|
||
# 安全检查:防止路径遍历攻击
|
||
if '..' in filename or '/' in filename or '\\' in filename:
|
||
return jsonify({"error": "无效的文件名"}), 400
|
||
|
||
# 检查文件是否存在
|
||
file_path = os.path.join(logo_dir, filename)
|
||
if not os.path.exists(file_path):
|
||
print(f"图标文件不存在: {file_path}")
|
||
return jsonify({"error": f"图标文件未找到: {filename}"}), 404
|
||
|
||
# 检查目录是否存在
|
||
if not os.path.exists(logo_dir):
|
||
print(f"图标目录不存在: {logo_dir}")
|
||
return jsonify({"error": "图标目录未找到"}), 404
|
||
|
||
return send_from_directory(logo_dir, filename)
|
||
except Exception as e:
|
||
print(f"获取图标文件出错: {e}")
|
||
print(f"尝试访问的文件: {os.path.join(logo_dir, filename)}")
|
||
return jsonify({"error": f"图标文件未找到: {filename}"}), 404
|
||
|
||
@app.route('/api/random-background', methods=['GET'])
|
||
def get_random_background():
|
||
"""获取随机背景图片"""
|
||
try:
|
||
# 获取背景图片目录中的所有图片
|
||
if os.path.exists(BACKGROUND_DIR) and os.path.isdir(BACKGROUND_DIR):
|
||
images = [f for f in os.listdir(BACKGROUND_DIR)
|
||
if f.lower().endswith(('.png', '.jpg', '.jpeg', '.webp', '.gif'))]
|
||
if images:
|
||
random_image = random.choice(images)
|
||
# 返回完整的 API 路径
|
||
return jsonify({"image": f"/api/background/{random_image}"})
|
||
else:
|
||
print(f"背景图片目录不存在: {BACKGROUND_DIR}")
|
||
return jsonify({"image": None})
|
||
except Exception as e:
|
||
print(f"获取随机背景出错: {e}")
|
||
print(f"背景目录路径: {BACKGROUND_DIR}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return jsonify({"image": None})
|
||
|
||
@app.route('/api/background/<filename>', methods=['GET'])
|
||
def get_background_image(filename):
|
||
"""提供背景图片文件"""
|
||
try:
|
||
# 安全检查:防止路径遍历攻击
|
||
if '..' in filename or '/' in filename or '\\' in filename:
|
||
return jsonify({"error": "无效的文件名"}), 400
|
||
|
||
# 检查目录是否存在
|
||
if not os.path.exists(BACKGROUND_DIR):
|
||
print(f"背景图片目录不存在: {BACKGROUND_DIR}")
|
||
return jsonify({"error": "背景图片目录未找到"}), 404
|
||
|
||
# 检查文件是否存在
|
||
file_path = os.path.join(BACKGROUND_DIR, filename)
|
||
if not os.path.exists(file_path):
|
||
print(f"背景图片文件不存在: {file_path}")
|
||
return jsonify({"error": f"背景图片未找到: {filename}"}), 404
|
||
|
||
return send_from_directory(BACKGROUND_DIR, filename)
|
||
except Exception as e:
|
||
print(f"获取背景图片出错: {e}")
|
||
return jsonify({"error": f"背景图片未找到: {filename}"}), 404
|
||
|
||
@app.route('/api/all', methods=['GET'])
|
||
def get_all():
|
||
"""获取所有数据"""
|
||
profile = load_json_file('profile.json')
|
||
projects = load_json_file('projects.json')
|
||
contacts = load_json_file('contacts.json')
|
||
techstack = load_json_file('techstack.json')
|
||
|
||
return jsonify({
|
||
"profile": profile,
|
||
"techstack": techstack,
|
||
"projects": projects,
|
||
"contacts": contacts
|
||
})
|
||
|
||
@app.route('/', methods=['GET'])
|
||
def index():
|
||
"""服务前端页面或API信息"""
|
||
if RUN_MODE == 'production' and app.static_folder and os.path.exists(os.path.join(app.static_folder, 'index.html')):
|
||
# 生产环境,返回前端页面(如果存在)
|
||
try:
|
||
return send_from_directory(app.static_folder, 'index.html')
|
||
except:
|
||
pass
|
||
|
||
# 返回API信息
|
||
return jsonify({
|
||
"message": "萌芽主页 后端API",
|
||
"author": "树萌芽",
|
||
"version": "1.0.0",
|
||
"mode": RUN_MODE,
|
||
"note": "这是一个纯后端API服务,前端请访问独立的前端应用",
|
||
"api_base": "https://nav.api.shumengya.top/api",
|
||
"endpoints": {
|
||
"/api/profile": "获取个人信息",
|
||
"/api/techstack": "获取技术栈",
|
||
"/api/projects": "获取项目列表",
|
||
"/api/contacts": "获取联系方式",
|
||
"/api/random-background": "获取随机背景图片",
|
||
"/api/all": "获取所有数据"
|
||
}
|
||
})
|
||
|
||
@app.route('/admin')
|
||
def admin():
|
||
"""服务管理员页面(也是前端)"""
|
||
if RUN_MODE == 'production' and app.static_folder and os.path.exists(os.path.join(app.static_folder, 'index.html')):
|
||
try:
|
||
return send_from_directory(app.static_folder, 'index.html')
|
||
except:
|
||
pass
|
||
|
||
return jsonify({
|
||
"error": "管理员页面未找到",
|
||
"note": "这是一个纯后端API服务,请访问独立的前端应用",
|
||
"api_base": "https://nav.api.shumengya.top/api"
|
||
}), 404
|
||
|
||
@app.route('/api')
|
||
def api_info():
|
||
"""API信息"""
|
||
return jsonify({
|
||
"message": "萌芽主页 后端API",
|
||
"author":"树萌芽",
|
||
"version": "1.0.0",
|
||
"endpoints": {
|
||
"/api/profile": "获取个人信息",
|
||
"/api/techstack": "获取技术栈",
|
||
"/api/projects": "获取项目列表",
|
||
"/api/contacts": "获取联系方式",
|
||
"/api/random-background": "获取随机背景图片",
|
||
"/api/all": "获取所有数据"
|
||
}
|
||
})
|
||
|
||
# 处理404错误
|
||
@app.errorhandler(404)
|
||
def not_found(e):
|
||
"""处理404错误"""
|
||
# 检查是否为API请求
|
||
if request.path.startswith('/api'):
|
||
return jsonify({"error": "API endpoint not found"}), 404
|
||
|
||
# 非API请求 - 如果是前后端分离,返回API信息
|
||
if RUN_MODE == 'production' and app.static_folder and os.path.exists(os.path.join(app.static_folder, 'index.html')):
|
||
# 如果有前端构建文件,尝试返回
|
||
try:
|
||
return send_from_directory(app.static_folder, 'index.html')
|
||
except:
|
||
pass
|
||
|
||
# 返回API信息
|
||
return jsonify({
|
||
"error": "页面未找到",
|
||
"message": "这是一个纯后端API服务",
|
||
"api_base": "https://nav.api.shumengya.top/api",
|
||
"endpoints": {
|
||
"/api/profile": "获取个人信息",
|
||
"/api/techstack": "获取技术栈",
|
||
"/api/projects": "获取项目列表",
|
||
"/api/contacts": "获取联系方式",
|
||
"/api/random-background": "获取随机背景图片",
|
||
"/api/all": "获取所有数据"
|
||
}
|
||
}), 404
|
||
|
||
if __name__ == '__main__':
|
||
# 从环境变量获取端口,默认为 5000
|
||
port = int(os.environ.get('PORT', 5000))
|
||
# 生产环境关闭 debug 模式
|
||
debug_mode = RUN_MODE != 'production'
|
||
app.run(debug=debug_mode, host='0.0.0.0', port=port)
|