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

This commit is contained in:
2026-03-12 18:58:55 +08:00
parent 4573a21f88
commit c903101d86
39 changed files with 1112 additions and 483 deletions

View 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

View 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"]

View File

@@ -19,7 +19,7 @@ python app.py
## API 接口
- `GET /api/profile` - 获取个人基本信息
- `GET /api/projects` - 获取精选项目列表
- `GET /api/projects` - 获取全部项目列表
- `GET /api/contacts` - 获取联系方式
- `GET /api/all` - 获取所有数据

View File

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

View 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

View 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()