chore: sync local changes (2026-03-12)
This commit is contained in:
18
mengyaprofile-backend/.dockerignore
Normal file
18
mengyaprofile-backend/.dockerignore
Normal file
@@ -0,0 +1,18 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
*.so
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
.env
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
*.md
|
||||
28
mengyaprofile-backend/Dockerfile
Normal file
28
mengyaprofile-backend/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
||||
# 使用 Python 官方镜像
|
||||
FROM python:3.11-slim
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 设置环境变量
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV RUN_MODE=production
|
||||
ENV DATA_DIR=/app/data
|
||||
|
||||
# 复制依赖文件
|
||||
COPY requirements.txt .
|
||||
|
||||
# 安装依赖
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 复制应用代码
|
||||
COPY app.py .
|
||||
|
||||
# 创建数据目录(如果挂载了外部卷,这个目录会被覆盖)
|
||||
RUN mkdir -p /app/data/logo
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 5000
|
||||
|
||||
# 启动应用
|
||||
CMD ["python", "app.py"]
|
||||
@@ -19,7 +19,7 @@ python app.py
|
||||
## API 接口
|
||||
|
||||
- `GET /api/profile` - 获取个人基本信息
|
||||
- `GET /api/projects` - 获取精选项目列表
|
||||
- `GET /api/projects` - 获取全部项目列表
|
||||
- `GET /api/contacts` - 获取联系方式
|
||||
- `GET /api/all` - 获取所有数据
|
||||
|
||||
|
||||
@@ -7,22 +7,27 @@ 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'))
|
||||
|
||||
# 根据运行模式配置
|
||||
if RUN_MODE == 'production':
|
||||
# 生产环境:使用构建后的前端
|
||||
FRONTEND_BUILD_PATH = os.path.join(os.path.dirname(__file__), '..', 'frontend', 'build')
|
||||
# 检查是否有前端构建文件(前后端分离时可能没有)
|
||||
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='')
|
||||
BACKGROUND_DIR = os.path.join(FRONTEND_BUILD_PATH, 'background')
|
||||
else:
|
||||
# 开发环境:不服务前端,只提供 API
|
||||
# 开发环境或纯后端模式:只提供 API
|
||||
app = Flask(__name__)
|
||||
BACKGROUND_DIR = os.path.join(os.path.dirname(__file__), '..', 'mengyaprofile-frontend', 'public', 'background')
|
||||
|
||||
CORS(app) # 允许跨域请求
|
||||
|
||||
# 数据文件路径 - 支持环境变量配置
|
||||
DATA_DIR = os.environ.get('DATA_DIR', os.path.join(os.path.dirname(__file__), 'data'))
|
||||
|
||||
def load_json_file(filename):
|
||||
"""加载JSON文件"""
|
||||
try:
|
||||
@@ -44,7 +49,7 @@ def get_profile():
|
||||
|
||||
@app.route('/api/projects', methods=['GET'])
|
||||
def get_projects():
|
||||
"""获取精选项目列表"""
|
||||
"""获取全部项目列表"""
|
||||
data = load_json_file('projects.json')
|
||||
if data:
|
||||
return jsonify(data)
|
||||
@@ -66,22 +71,78 @@ def get_techstack():
|
||||
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):
|
||||
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)
|
||||
return jsonify({"image": f"/background/{random_image}"})
|
||||
# 返回完整的 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():
|
||||
"""获取所有数据"""
|
||||
@@ -100,37 +161,45 @@ def get_all():
|
||||
@app.route('/', methods=['GET'])
|
||||
def index():
|
||||
"""服务前端页面或API信息"""
|
||||
if RUN_MODE == 'production' and app.static_folder:
|
||||
# 生产环境,返回前端页面
|
||||
return send_from_directory(app.static_folder, 'index.html')
|
||||
else:
|
||||
# 开发环境,返回API信息
|
||||
return jsonify({
|
||||
"message": "萌芽主页 后端API - 开发模式",
|
||||
"author": "树萌芽",
|
||||
"version": "1.0.0",
|
||||
"mode": "development",
|
||||
"note": "前端开发服务器运行在 http://localhost:3000",
|
||||
"endpoints": {
|
||||
"/api/profile": "获取个人信息",
|
||||
"/api/techstack": "获取技术栈",
|
||||
"/api/projects": "获取项目列表",
|
||||
"/api/contacts": "获取联系方式",
|
||||
"/api/random-background": "获取随机背景图片",
|
||||
"/api/all": "获取所有数据"
|
||||
}
|
||||
})
|
||||
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:
|
||||
return send_from_directory(app.static_folder, 'index.html')
|
||||
else:
|
||||
return jsonify({
|
||||
"error": "开发模式",
|
||||
"note": "请访问 http://localhost:3000/admin?token=shumengya520"
|
||||
}), 404
|
||||
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():
|
||||
@@ -149,7 +218,7 @@ def api_info():
|
||||
}
|
||||
})
|
||||
|
||||
# 处理前端路由 - 所有非API请求都返回 index.html
|
||||
# 处理404错误
|
||||
@app.errorhandler(404)
|
||||
def not_found(e):
|
||||
"""处理404错误"""
|
||||
@@ -157,17 +226,32 @@ def not_found(e):
|
||||
if request.path.startswith('/api'):
|
||||
return jsonify({"error": "API endpoint not found"}), 404
|
||||
|
||||
# 非API请求
|
||||
if RUN_MODE == 'production' and app.static_folder:
|
||||
# 生产环境返回前端页面(支持前端路由)
|
||||
return send_from_directory(app.static_folder, 'index.html')
|
||||
# 非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": "页面未找到",
|
||||
"mode": "development",
|
||||
"note": "开发环境请访问 http://localhost:3000"
|
||||
"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__':
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||
# 从环境变量获取端口,默认为 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)
|
||||
|
||||
22
mengyaprofile-backend/docker-compose.yml
Normal file
22
mengyaprofile-backend/docker-compose.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
mengyaprofile-backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: mengyaprofile-backend
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "1616:5000"
|
||||
volumes:
|
||||
- /shumengya/docker/mengyaprofile-backend/data:/app/data
|
||||
environment:
|
||||
- RUN_MODE=production
|
||||
- DATA_DIR=/app/data
|
||||
networks:
|
||||
- mengyaprofile-network
|
||||
|
||||
networks:
|
||||
mengyaprofile-network:
|
||||
driver: bridge
|
||||
59
mengyaprofile-backend/sort_techstack.py
Normal file
59
mengyaprofile-backend/sort_techstack.py
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
技术栈排序和更新脚本
|
||||
- 按照技术栈名称的首字母排序
|
||||
- 添加新的技术栈项
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
def sort_and_update_techstack():
|
||||
# 文件路径
|
||||
file_path = os.path.join(os.path.dirname(__file__), 'data', 'techstack.json')
|
||||
|
||||
# 读取JSON文件
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# 新添加的技术栈项
|
||||
new_items = [
|
||||
{
|
||||
"name": "Spring",
|
||||
"icon": "https://img.shields.io/badge/-Spring-6DB33F?style=flat&logo=spring&logoColor=white",
|
||||
"link": "https://spring.io/"
|
||||
},
|
||||
{
|
||||
"name": "Gin",
|
||||
"icon": "https://img.shields.io/badge/-Gin-00ADD8?style=flat&logo=go&logoColor=white",
|
||||
"link": "https://gin-gonic.com/"
|
||||
}
|
||||
]
|
||||
|
||||
# 获取现有项的名称集合,用于检查是否已存在
|
||||
existing_names = {item['name'] for item in data['items']}
|
||||
|
||||
# 添加新项(如果不存在)
|
||||
for new_item in new_items:
|
||||
if new_item['name'] not in existing_names:
|
||||
data['items'].append(new_item)
|
||||
print(f"已添加: {new_item['name']}")
|
||||
else:
|
||||
print(f"已存在,跳过: {new_item['name']}")
|
||||
|
||||
# 按照名称的首字母排序(不区分大小写)
|
||||
data['items'].sort(key=lambda x: x['name'].upper())
|
||||
|
||||
# 写回文件,保持格式美观
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n排序完成!共 {len(data['items'])} 个技术栈项")
|
||||
print("技术栈列表(按首字母排序):")
|
||||
for i, item in enumerate(data['items'], 1):
|
||||
print(f" {i}. {item['name']}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
sort_and_update_techstack()
|
||||
|
||||
Reference in New Issue
Block a user