yexingyangPDF / app.py
quejing's picture
Upload 36 files
4cbd2e8 verified
Raw
History Blame Contribute Delete
5.15 kB
"""
叶兴阳双语音标有声读物 PDF 下载站
Flask 路由器 — 干净 URL + SPA 回退 + API 接口
"""
from flask import Flask, send_file, jsonify, request, abort, send_from_directory
import os
import json
app = Flask(__name__, static_folder='.', static_url_path='')
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# 预读索引,加快 /api/sources 响应
with open(os.path.join(BASE_DIR, 'data', 'index.json'), 'r', encoding='utf-8') as f:
INDEX = json.load(f)
# 构建 id → source 的快速查找
ID_MAP = {s['id']: s for s in INDEX['sources']}
# ─── 页面路由 ────────────────────────────────────────
@app.route('/')
@app.route('/index.html')
def home():
return send_file('index.html')
@app.route('/reader')
def reader():
return send_file('reader.html')
@app.route('/category/<cat_id>')
def category(cat_id):
"""干净 URL 分类页 — 回退到 index.html,前端 JS 识别路由"""
if cat_id not in ID_MAP:
abort(404)
return send_file('index.html')
# ─── 数据 API ────────────────────────────────────────
@app.route('/api/sources')
def api_sources():
"""返回所有分类的基本信息(不含具体文件)"""
return jsonify(INDEX)
@app.route('/api/source/<cat_id>')
def api_source(cat_id):
"""返回单个分类的元信息"""
if cat_id not in ID_MAP:
return jsonify({'error': '分类不存在'}), 404
src = dict(ID_MAP[cat_id])
return jsonify(src)
@app.route('/api/files/<cat_id>')
def api_files(cat_id):
"""返回某个分类的全部文件列表(按需合并分片)"""
if cat_id not in ID_MAP:
return jsonify({'error': '分类不存在'}), 404
src = ID_MAP[cat_id]
all_files = []
for chunk in src['chunks']:
chunk_path = os.path.join(BASE_DIR, 'data', chunk['f'])
if os.path.isfile(chunk_path):
with open(chunk_path, 'r', encoding='utf-8') as f:
all_files.extend(json.load(f))
return jsonify(all_files)
@app.route('/api/search/<cat_id>')
def api_search(cat_id):
"""搜索指定分类的文件"""
q = request.args.get('q', '').lower().strip()
if cat_id not in ID_MAP:
return jsonify({'error': '分类不存在'}), 404
src = ID_MAP[cat_id]
results = []
for chunk in src['chunks']:
chunk_path = os.path.join(BASE_DIR, 'data', chunk['f'])
if os.path.isfile(chunk_path):
with open(chunk_path, 'r', encoding='utf-8') as f:
items = json.load(f)
results.extend([item for item in items if q in item[0].lower()])
return jsonify(results)
@app.route('/api/health')
def api_health():
return jsonify({'status': 'ok', 'sources': len(INDEX['sources'])})
# ─── 静态文件 ────────────────────────────────────────
@app.route('/data/<path:filename>')
def serve_data(filename):
"""托管 data/ 目录下的 JSON 数据文件"""
data_dir = os.path.join(BASE_DIR, 'data')
return send_from_directory(data_dir, filename)
@app.route('/favicon.ico')
def favicon():
return '', 204
# ─── 404 ────────────────────────────────────────────
@app.errorhandler(404)
def not_found(e):
return '''
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>404 - 页面不存在</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,"Microsoft YaHei",sans-serif;
background:#f8fafc;display:flex;align-items:center;justify-content:center;min-height:100vh;color:#1e293b}
.box{text-align:center;padding:3rem}
.box h1{font-size:5rem;font-weight:800;color:#6366f1;line-height:1}
.box p{color:#64748b;margin:1rem 0 1.5rem;font-size:1rem}
.box a{display:inline-block;padding:0.65rem 2rem;background:linear-gradient(135deg,#6366f1,#4f46e5);
color:white;text-decoration:none;border-radius:12px;font-weight:600;font-size:0.95rem;transition:all .15s}
.box a:hover{transform:translateY(-1px);box-shadow:0 4px 16px rgba(99,102,241,0.3)}
</style></head>
<body><div class="box">
<h1>404</h1><p>页面不存在</p>
<a href="/">← 返回首页</a>
</div></body></html>
''', 404
# ─── 启动 ────────────────────────────────────────────
if __name__ == '__main__':
print(f'Server starting at http://0.0.0.0:7860')
print(f'Routes: / /category/<id> /reader /api/sources /api/files/<id> /api/search/<id>?q=')
app.run(host='0.0.0.0', port=7860, debug=False)