233 lines
7.3 KiB
Python
233 lines
7.3 KiB
Python
import os
|
|
import hashlib
|
|
import secrets
|
|
from datetime import datetime
|
|
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, session, abort, jsonify
|
|
from werkzeug.utils import secure_filename
|
|
import markdown
|
|
import json
|
|
|
|
app = Flask(__name__)
|
|
app.secret_key = secrets.token_hex(32)
|
|
app.config['UPLOAD_FOLDER'] = 'uploads'
|
|
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB
|
|
app.config['ARTICLES_FOLDER'] = 'docs'
|
|
app.config['ALLOWED_EXTENSIONS'] = {
|
|
'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp',
|
|
'mp4', 'webm', 'ogg', 'mov',
|
|
'mp3', 'wav', 'ogg', 'm4a',
|
|
'pdf', 'doc', 'docx', 'txt', 'md',
|
|
'zip', 'rar', '7z'
|
|
}
|
|
|
|
# 确保文件夹存在
|
|
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
|
os.makedirs(app.config['ARTICLES_FOLDER'], exist_ok=True)
|
|
|
|
# 管理员密码的SHA256哈希
|
|
ADMIN_PASSWORD_HASH = '7ea8aa746eaaa6dfbe7f1cde97c0ecca9afa204d071c487fb4f7debb5fcd301e'
|
|
|
|
def check_password(password):
|
|
"""检查密码是否正确"""
|
|
return hashlib.sha256(password.encode()).hexdigest() == ADMIN_PASSWORD_HASH
|
|
|
|
def allowed_file(filename):
|
|
"""检查文件扩展名是否允许"""
|
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
|
|
|
|
def get_articles():
|
|
"""获取所有文章信息"""
|
|
articles = []
|
|
if os.path.exists('articles.json'):
|
|
with open('articles.json', 'r', encoding='utf-8-sig') as f:
|
|
articles = json.load(f)
|
|
return articles
|
|
|
|
def save_articles(articles):
|
|
"""保存文章信息"""
|
|
with open('articles.json', 'w', encoding='utf-8') as f:
|
|
json.dump(articles, f, ensure_ascii=False, indent=2)
|
|
|
|
def generate_random_filename():
|
|
"""生成随机文件名"""
|
|
return secrets.token_urlsafe(16)
|
|
|
|
# 提供CSS文件
|
|
@app.route('/css.css')
|
|
def serve_css():
|
|
"""提供css.css样式文件"""
|
|
return send_from_directory('templates', 'css.css')
|
|
|
|
# 首页
|
|
@app.route('/')
|
|
def index():
|
|
"""首页显示所有文章"""
|
|
articles = get_articles()
|
|
articles.sort(key=lambda x: x.get('created_at', ''), reverse=True)
|
|
|
|
return render_template('index.html', articles=articles)
|
|
|
|
@app.route('/docs/<path:filename>')
|
|
def show_article(filename):
|
|
"""显示文章内容"""
|
|
article_path = os.path.join(app.config['ARTICLES_FOLDER'], filename)
|
|
|
|
if not os.path.exists(article_path):
|
|
abort(404)
|
|
|
|
articles = get_articles()
|
|
article_info = None
|
|
for article in articles:
|
|
if article.get('filename') == filename:
|
|
article_info = article
|
|
break
|
|
|
|
if not article_info:
|
|
abort(404)
|
|
|
|
with open(article_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
html_content = markdown.markdown(content, extensions=[
|
|
'markdown.extensions.extra',
|
|
'markdown.extensions.codehilite',
|
|
'markdown.extensions.tables',
|
|
'markdown.extensions.toc'
|
|
])
|
|
|
|
return render_template('article.html',
|
|
content=html_content,
|
|
title=article_info.get('title', '文章'),
|
|
created_at=article_info.get('created_at', ''),
|
|
author=article_info.get('author', '管理员'))
|
|
|
|
@app.route('/admin', methods=['GET', 'POST'])
|
|
def admin_login():
|
|
"""管理员登录"""
|
|
if request.method == 'POST':
|
|
password = request.form.get('password', '')
|
|
if check_password(password):
|
|
session['admin_logged_in'] = True
|
|
return redirect(url_for('admin_dashboard'))
|
|
else:
|
|
return render_template('admin_login.html', error='密码错误')
|
|
|
|
return render_template('admin_login.html')
|
|
|
|
@app.route('/admin/dashboard')
|
|
def admin_dashboard():
|
|
"""管理员仪表板"""
|
|
if not session.get('admin_logged_in'):
|
|
return redirect(url_for('admin_login'))
|
|
|
|
articles = get_articles()
|
|
return render_template('admin.html', articles=articles)
|
|
|
|
@app.route('/uploads/<path:filename>')
|
|
def uploaded_file(filename):
|
|
"""提供上传的文件"""
|
|
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
|
|
|
|
@app.route('/admin/logout')
|
|
def admin_logout():
|
|
"""退出登录"""
|
|
session.pop('admin_logged_in', None)
|
|
return redirect(url_for('index'))
|
|
|
|
@app.route('/admin/create_article', methods=['POST'])
|
|
def create_article():
|
|
"""创建新文章"""
|
|
if not session.get('admin_logged_in'):
|
|
return jsonify({'error': '未登录'}), 401
|
|
|
|
title = request.form.get('title', '').strip()
|
|
content = request.form.get('content', '').strip()
|
|
|
|
if not title or not content:
|
|
return jsonify({'error': '标题和内容不能为空'}), 400
|
|
|
|
# 生成随机文件名
|
|
filename = generate_random_filename() + '.md'
|
|
article_path = os.path.join(app.config['ARTICLES_FOLDER'], filename)
|
|
|
|
# 保存文章内容
|
|
with open(article_path, 'w', encoding='utf-8') as f:
|
|
f.write(content)
|
|
|
|
# 保存文章信息
|
|
articles = get_articles()
|
|
articles.append({
|
|
'id': len(articles) + 1,
|
|
'title': title,
|
|
'filename': filename,
|
|
'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
'author': 'Dvs (DvsXT)'
|
|
})
|
|
save_articles(articles)
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'filename': filename,
|
|
'message': '文章创建成功'
|
|
})
|
|
|
|
@app.route('/admin/upload_file', methods=['POST'])
|
|
def upload_file():
|
|
"""上传文件"""
|
|
if not session.get('admin_logged_in'):
|
|
return jsonify({'error': '未登录'}), 401
|
|
|
|
if 'file' not in request.files:
|
|
return jsonify({'error': '没有文件'}), 400
|
|
|
|
file = request.files['file']
|
|
if file.filename == '':
|
|
return jsonify({'error': '没有选择文件'}), 400
|
|
|
|
if file and allowed_file(file.filename):
|
|
filename = secure_filename(file.filename)
|
|
# 添加时间戳避免重名
|
|
name, ext = os.path.splitext(filename)
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
filename = f"{name}_{timestamp}{ext}"
|
|
|
|
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
|
|
|
|
# 返回文件的URL
|
|
file_url = url_for('uploaded_file', filename=filename, _external=True)
|
|
return jsonify({
|
|
'success': True,
|
|
'url': file_url,
|
|
'filename': filename
|
|
})
|
|
|
|
return jsonify({'error': '不支持的文件类型'}), 400
|
|
|
|
@app.route('/admin/delete_article', methods=['POST'])
|
|
def delete_article():
|
|
"""删除文章"""
|
|
if not session.get('admin_logged_in'):
|
|
return jsonify({'error': '未登录'}), 401
|
|
|
|
filename = request.form.get('filename', '').strip()
|
|
if not filename:
|
|
return jsonify({'error': '文件名不能为空'}), 400
|
|
|
|
# 删除文章文件
|
|
article_path = os.path.join(app.config['ARTICLES_FOLDER'], filename)
|
|
if os.path.exists(article_path):
|
|
os.remove(article_path)
|
|
|
|
# 从文章中移除
|
|
articles = get_articles()
|
|
articles = [article for article in articles if article.get('filename') != filename]
|
|
save_articles(articles)
|
|
|
|
return jsonify({'success': True, 'message': '文章删除成功'})
|
|
|
|
if __name__ == '__main__':
|
|
# 初始化articles.json文件
|
|
if not os.path.exists('articles.json'):
|
|
save_articles([])
|
|
|
|
app.run(debug=False, port=50, host='0.0.0.0') |