Initial commit: DVS Blog v1.0.0
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
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')
|
||||
Reference in New Issue
Block a user