1. Flask应用代码 (app.py)
from flask import Flask, redirect, request, jsonify
import random
import oss2
import configparser
import os
from pathlib import Path
app = Flask(__name__)
def load_config():
"""加载配置文件"""
config = configparser.ConfigParser()
config_path = os.path.join(os.getcwd(), 'config.ini')
if not os.path.exists(config_path):
raise FileNotFoundError("未找到 config.ini 配置文件")
config.read(config_path)
# 验证必要配置项
required_keys = {
'oss': ['access_key_id', 'access_key_secret', 'endpoint', 'bucket_name']
}
for section, keys in required_keys.items():
if not config.has_section(section):
raise ValueError(f"配置文件中缺少 [{section}] 部分")
for key in keys:
if not config.has_option(section, key):
raise ValueError(f"配置文件中缺少 [{section}] 部分的 {key} 配置项")
return config
# 加载配置
try:
config = load_config()
OSS_ACCESS_KEY_ID = config.get('oss', 'access_key_id')
OSS_ACCESS_KEY_SECRET = config.get('oss', 'access_key_secret')
OSS_ENDPOINT = config.get('oss', 'endpoint')
OSS_BUCKET_NAME = config.get('oss', 'bucket_name')
OSS_BASE_PATH = config.get('oss', 'base_path', fallback='images/')
except Exception as e:
app.logger.error(f'配置加载失败: {str(e)}')
raise
# 初始化OSS客户端
auth = oss2.Auth(OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET)
bucket = oss2.Bucket(auth, OSS_ENDPOINT, OSS_BUCKET_NAME)
def list_all_images(prefix):
"""递归列出指定前缀下的所有图片文件"""
image_files = []
# 先列出当前目录下的文件
for obj in oss2.ObjectIterator(bucket, prefix=prefix, delimiter=''):
if obj.key.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')):
image_files.append(obj.key)
# 再递归处理子目录
for obj in oss2.ObjectIterator(bucket, prefix=prefix, delimiter='/'):
if obj.is_prefix(): # 这是一个子目录
subdir = obj.key
image_files.extend(list_all_images(subdir))
return image_files
def get_random_image(category=None):
"""从指定分类中获取随机图片URL(支持子目录)"""
prefix = OSS_BASE_PATH
if category:
prefix = f'{OSS_BASE_PATH}{category}/'
try:
# 获取所有图片文件(包括子目录中的)
all_images = list_all_images(prefix)
if not all_images:
if category:
return get_random_image(None) # 回退到无分类模式
return None
# 随机选择一个文件
random_file = random.choice(all_images)
return f"https://{OSS_BUCKET_NAME}.{OSS_ENDPOINT}/{random_file}"
except Exception as e:
app.logger.error(f"获取随机图片失败: {str(e)}")
return None
@app.route('/random')
def random_image():
"""随机图片API"""
category = request.args.get('category')
image_url = get_random_image(category)
if image_url:
return redirect(image_url, code=302)
else:
return jsonify({
"error": "No images found",
"status": 404
}), 404
@app.route('/categories')
def list_categories():
"""列出所有一级分类"""
try:
categories = set()
for obj in oss2.ObjectIterator(bucket, prefix=OSS_BASE_PATH, delimiter='/'):
if obj.is_prefix():
# 提取一级分类名
relative_path = obj.key[len(OSS_BASE_PATH):-1]
first_level = relative_path.split('/')[0]
categories.add(first_level)
return jsonify({
"categories": sorted(list(categories)),
"status": 200
})
except Exception as e:
app.logger.error(f"获取分类列表失败: {str(e)}")
return jsonify({
"error": "Failed to list categories",
"status": 500
}), 500
@app.route('/subcategories')
def list_subcategories():
"""列出指定分类下的子分类"""
try:
category = request.args.get('category')
if not category:
return jsonify({
"error": "Category parameter is required",
"status": 400
}), 400
prefix = f'{OSS_BASE_PATH}{category}/'
subcategories = set()
# 查找一级子目录
for obj in oss2.ObjectIterator(bucket, prefix=prefix, delimiter='/'):
if obj.is_prefix():
# 提取子分类名(相对于当前分类)
sub_path = obj.key[len(prefix):-1]
if sub_path: # 确保不是空字符串
subcategories.add(sub_path.split('/')[0]) # 只取第一级子目录
return jsonify({
"category": category,
"subcategories": sorted(list(subcategories)),
"status": 200
})
except Exception as e:
app.logger.error(f"获取子分类列表失败: {str(e)}")
return jsonify({
"error": "Failed to list subcategories",
"status": 500
}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
2. 配置文件示例 (config.ini)
[oss]
access_key_id = your_access_key_id
access_key_secret = your_access_key_secret
endpoint = oss-cn-hangzhou.aliyuncs.com
bucket_name = your-bucket-name
base_path = images/ ; 可选,默认为 images/
3. OSS目录结构示例
images/
├── category1/
│ ├── subcategory1/
│ └── subcategory2/
├── category2/
└── category3/
└── subcategory3/
4. 部署建议
# 1. 创建虚拟环境
python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
# 2. 安装依赖
pip install flask oss2 configparser
# 3. 创建并编辑配置文件
cp config.example.ini config.ini
# 然后编辑 config.ini 填入你的OSS配置
# 4. 运行应用
python app.py
# 5. 生产环境建议使用WSGI服务器
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:5000 app:app
5. 安全注意事项
将config.ini添加到.gitignore中,避免将敏感信息提交到版本控制
配置文件权限设置为仅当前用户可读:
chmod 600 config.ini
定期轮换AccessKey,避免长期使用同一组密钥
6. API使用示例
获取随机动漫图片(包括所有子目录):
GET /random?category=dongman
列出所有一级分类:
GET /categories
列出动漫分类下的子分类:
GET /subcategories?category=dongman
7. 系统服务配置 (Linux Systemd)
[Unit]
Description=Random Image API Service
After=network.target
[Service]
User=your_username
Group=your_groupname
WorkingDirectory=/path/to/your/project
Environment="PATH=/path/to/your/project/venv/bin"
ExecStart=/path/to/your/project/venv/bin/gunicorn --workers 3 --bind 0.0.0.0:5000 app:app
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
8. 日志轮替配置 (Logrotate)
/var/log/randomimgapi/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0640 ubuntu ubuntu
sharedscripts
postrotate
systemctl reload randomimgapi >/dev/null 2>&1 || true
endscript
}
9. 维护命令
# Linux系统
sudo systemctl restart randomimgapi # 重启服务
sudo systemctl stop randomimgapi # 停止服务
sudo systemctl disable randomimgapi # 禁用开机启动
journalctl -u randomimgapi -f # 查看日志
# Windows系统
nssm restart randomimgapi # 重启服务
nssm stop randomimgapi # 停止服务
nssm remove randomimgapi confirm # 删除服务
10. 高级配置选项
Gunicorn配置文件示例 (gunicorn_config.py):
bind = "0.0.0.0:5000"
workers = 3
worker_class = "gevent"
timeout = 120
keepalive = 5
loglevel = "info"
accesslog = "/var/log/randomimgapi/access.log"
errorlog = "/var/log/randomimgapi/error.log"
环境变量配置:
[Service]
EnvironmentFile=/path/to/your/project/.env