Files
article-writer/app.py

683 lines
25 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
文章编写系统 - Article Writer System
功能主题管理、素材收集、Prompt编辑、大模型生成文章、分类与标签
"""
import os
import json
import uuid
import time
import sqlite3
import requests
from datetime import datetime
from flask import Flask, render_template, request, jsonify, redirect, url_for, send_from_directory
from werkzeug.utils import secure_filename
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
UPLOAD_DIR = os.path.join(BASE_DIR, 'static', 'uploads')
DB_PATH = os.path.join(BASE_DIR, 'articles.db')
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp'}
# LLM配置通过环境变量读取避免密钥泄露运行时可从数据库覆盖
LLM_URL = os.environ.get('LLM_URL', 'https://www.autodl.art/api/v1/chat/completions')
LLM_API_KEY = os.environ.get('LLM_API_KEY', '')
LLM_MODEL = os.environ.get('LLM_MODEL', 'qwen3.6-plus')
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB
def get_db():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")
return conn
def init_db():
conn = get_db()
conn.executescript('''
CREATE TABLE IF NOT EXISTS topics (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT DEFAULT '',
prompt TEXT DEFAULT '',
created_at TEXT DEFAULT (datetime('now','localtime')),
updated_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS materials (
id TEXT PRIMARY KEY,
topic_id TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'text',
content TEXT DEFAULT '',
file_path TEXT DEFAULT '',
sort_order INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now','localtime')),
FOREIGN KEY (topic_id) REFERENCES topics(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS articles (
id TEXT PRIMARY KEY,
topic_id TEXT NOT NULL,
content TEXT DEFAULT '',
prompt_used TEXT DEFAULT '',
model TEXT DEFAULT '',
llm_url TEXT DEFAULT '',
created_at TEXT DEFAULT (datetime('now','localtime')),
FOREIGN KEY (topic_id) REFERENCES topics(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS llm_config (
id INTEGER PRIMARY KEY CHECK (id = 1),
url TEXT DEFAULT '',
api_key TEXT DEFAULT '',
model TEXT DEFAULT '',
updated_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS prompt_history (
id TEXT PRIMARY KEY,
topic_id TEXT NOT NULL,
prompt TEXT NOT NULL DEFAULT '',
model TEXT DEFAULT '',
created_at TEXT DEFAULT (datetime('now','localtime')),
FOREIGN KEY (topic_id) REFERENCES topics(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS categories (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
sort_order INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS tags (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
created_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS topic_categories (
topic_id TEXT NOT NULL,
category_id TEXT NOT NULL,
PRIMARY KEY (topic_id, category_id),
FOREIGN KEY (topic_id) REFERENCES topics(id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS topic_tags (
topic_id TEXT NOT NULL,
tag_id TEXT NOT NULL,
PRIMARY KEY (topic_id, tag_id),
FOREIGN KEY (topic_id) REFERENCES topics(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);
''')
# 初始化默认配置
conn.execute('INSERT OR IGNORE INTO llm_config (id, url, api_key, model) VALUES (1, ?, ?, ?)',
(LLM_URL, LLM_API_KEY, LLM_MODEL))
conn.commit()
conn.close()
def get_llm_config():
"""从数据库读取LLM配置如无则用环境变量默认值"""
conn = get_db()
row = conn.execute('SELECT * FROM llm_config WHERE id=1').fetchone()
conn.close()
if row and row['url']:
return {'url': row['url'], 'api_key': row['api_key'], 'model': row['model'] or LLM_MODEL}
return {'url': LLM_URL, 'api_key': LLM_API_KEY, 'model': LLM_MODEL}
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def _topic_with_tags(conn, topic_row):
"""给 topic row 附上分类和标签信息"""
t = dict(topic_row)
cats = conn.execute(
'SELECT c.* FROM categories c JOIN topic_categories tc ON c.id=tc.category_id WHERE tc.topic_id=? ORDER BY c.sort_order',
(t['id'],)
).fetchall()
t['categories'] = [dict(c) for c in cats]
tags = conn.execute(
'SELECT tg.* FROM tags tg JOIN topic_tags tt ON tg.id=tt.tag_id WHERE tt.topic_id=? ORDER BY tg.name',
(t['id'],)
).fetchall()
t['tags'] = [dict(tg) for tg in tags]
return t
# ==================== 页面路由 ====================
@app.route('/')
def index():
conn = get_db()
topics = conn.execute(
'SELECT t.*, (SELECT COUNT(*) FROM materials WHERE topic_id=t.id) as mat_count '
'FROM topics t ORDER BY updated_at DESC'
).fetchall()
categories = conn.execute('SELECT * FROM categories ORDER BY sort_order, name').fetchall()
tags = conn.execute('SELECT * FROM tags ORDER BY name').fetchall()
# 附上分类和标签
topic_list = []
for t in topics:
topic_list.append(_topic_with_tags(conn, t))
conn.close()
return render_template('index.html', topics=topic_list, categories=categories, tags=tags)
@app.route('/topic/<topic_id>')
def topic_detail(topic_id):
conn = get_db()
topic = conn.execute('SELECT * FROM topics WHERE id=?', (topic_id,)).fetchone()
if not topic:
conn.close()
return redirect(url_for('index'))
topic = _topic_with_tags(conn, topic)
materials = conn.execute(
'SELECT * FROM materials WHERE topic_id=? ORDER BY sort_order, created_at',
(topic_id,)
).fetchall()
articles = conn.execute(
'SELECT * FROM articles WHERE topic_id=? ORDER BY created_at DESC',
(topic_id,)
).fetchall()
categories = conn.execute('SELECT * FROM categories ORDER BY sort_order, name').fetchall()
tags = conn.execute('SELECT * FROM tags ORDER BY name').fetchall()
conn.close()
return render_template('topic.html', topic=topic, materials=materials, articles=articles,
categories=categories, tags=tags)
# ==================== 主题API ====================
@app.route('/api/topics', methods=['POST'])
def create_topic():
data = request.get_json(force=True)
name = data.get('name', '').strip()
if not name:
return jsonify({'error': '主题名不能为空'}), 400
conn = get_db()
tid = str(uuid.uuid4())[:8]
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
conn.execute(
'INSERT INTO topics (id, name, description, prompt, created_at, updated_at) VALUES (?,?,?,?,?,?)',
(tid, name, data.get('description', ''), data.get('prompt', ''), now, now)
)
# 关联分类
for cat_id in data.get('category_ids', []):
conn.execute('INSERT OR IGNORE INTO topic_categories (topic_id, category_id) VALUES (?,?)', (tid, cat_id))
# 关联标签如果传了已有tag id
for tag_id in data.get('tag_ids', []):
conn.execute('INSERT OR IGNORE INTO topic_tags (topic_id, tag_id) VALUES (?,?)', (tid, tag_id))
# 处理新标签(按名称创建)
for tag_name in data.get('new_tags', []):
tag_name = tag_name.strip()
if not tag_name:
continue
existing = conn.execute('SELECT id FROM tags WHERE name=?', (tag_name,)).fetchone()
if existing:
conn.execute('INSERT OR IGNORE INTO topic_tags (topic_id, tag_id) VALUES (?,?)', (tid, existing['id']))
else:
tag_id = str(uuid.uuid4())[:8]
conn.execute('INSERT INTO tags (id, name, created_at) VALUES (?,?,?)', (tag_id, tag_name, now))
conn.execute('INSERT OR IGNORE INTO topic_tags (topic_id, tag_id) VALUES (?,?)', (tid, tag_id))
conn.commit()
topic = conn.execute('SELECT * FROM topics WHERE id=?', (tid,)).fetchone()
result = _topic_with_tags(conn, topic)
conn.close()
return jsonify(result)
@app.route('/api/topics/<topic_id>', methods=['PUT'])
def update_topic(topic_id):
data = request.get_json(force=True)
conn = get_db()
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
fields = []
values = []
for key in ['name', 'description', 'prompt']:
if key in data:
fields.append(f'{key}=?')
values.append(data[key])
if fields:
fields.append('updated_at=?')
values.append(now)
values.append(topic_id)
conn.execute(f'UPDATE topics SET {",".join(fields)} WHERE id=?', values)
conn.commit()
# 更新分类关联
if 'category_ids' in data:
conn.execute('DELETE FROM topic_categories WHERE topic_id=?', (topic_id,))
for cat_id in data['category_ids']:
conn.execute('INSERT OR IGNORE INTO topic_categories (topic_id, category_id) VALUES (?,?)', (topic_id, cat_id))
# 更新标签关联
if 'tag_ids' in data or 'new_tags' in data:
conn.execute('DELETE FROM topic_tags WHERE topic_id=?', (topic_id,))
for tag_id in data.get('tag_ids', []):
conn.execute('INSERT OR IGNORE INTO topic_tags (topic_id, tag_id) VALUES (?,?)', (topic_id, tag_id))
for tag_name in data.get('new_tags', []):
tag_name = tag_name.strip()
if not tag_name:
continue
existing = conn.execute('SELECT id FROM tags WHERE name=?', (tag_name,)).fetchone()
if existing:
conn.execute('INSERT OR IGNORE INTO topic_tags (topic_id, tag_id) VALUES (?,?)', (topic_id, existing['id']))
else:
tag_id = str(uuid.uuid4())[:8]
conn.execute('INSERT INTO tags (id, name, created_at) VALUES (?,?,?)', (tag_id, tag_name, now))
conn.execute('INSERT OR IGNORE INTO topic_tags (topic_id, tag_id) VALUES (?,?)', (topic_id, tag_id))
# 清理无引用的孤立标签
conn.execute('DELETE FROM tags WHERE id NOT IN (SELECT tag_id FROM topic_tags)')
conn.commit()
topic = conn.execute('SELECT * FROM topics WHERE id=?', (topic_id,)).fetchone()
result = _topic_with_tags(conn, topic)
conn.close()
return jsonify(result)
@app.route('/api/topics/<topic_id>', methods=['DELETE'])
def delete_topic(topic_id):
conn = get_db()
# 删除关联的素材文件
materials = conn.execute('SELECT file_path FROM materials WHERE topic_id=? AND type="image"', (topic_id,)).fetchall()
for m in materials:
if m['file_path']:
fp = os.path.join(BASE_DIR, 'static', m['file_path'])
if os.path.exists(fp):
os.remove(fp)
conn.execute('DELETE FROM topics WHERE id=?', (topic_id,))
# 清理孤立标签
conn.execute('DELETE FROM tags WHERE id NOT IN (SELECT tag_id FROM topic_tags)')
conn.commit()
conn.close()
return jsonify({'ok': True})
# ==================== 分类API ====================
@app.route('/api/categories', methods=['GET'])
def list_categories():
conn = get_db()
rows = conn.execute(
'SELECT c.*, (SELECT COUNT(*) FROM topic_categories WHERE category_id=c.id) as topic_count '
'FROM categories c ORDER BY c.sort_order, c.name'
).fetchall()
conn.close()
return jsonify([dict(r) for r in rows])
@app.route('/api/categories', methods=['POST'])
def create_category():
data = request.get_json(force=True)
name = data.get('name', '').strip()
if not name:
return jsonify({'error': '分类名不能为空'}), 400
conn = get_db()
existing = conn.execute('SELECT * FROM categories WHERE name=?', (name,)).fetchone()
if existing:
conn.close()
return jsonify({'error': '分类已存在'}), 400
cid = str(uuid.uuid4())[:8]
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
conn.execute('INSERT INTO categories (id, name, sort_order, created_at) VALUES (?,?,?,?)',
(cid, name, data.get('sort_order', 0), now))
conn.commit()
cat = conn.execute('SELECT * FROM categories WHERE id=?', (cid,)).fetchone()
conn.close()
return jsonify(dict(cat))
@app.route('/api/categories/<category_id>', methods=['PUT'])
def update_category(category_id):
data = request.get_json(force=True)
conn = get_db()
fields = []
values = []
for key in ['name', 'sort_order']:
if key in data:
fields.append(f'{key}=?')
values.append(data[key])
if fields:
values.append(category_id)
conn.execute(f'UPDATE categories SET {",".join(fields)} WHERE id=?', values)
conn.commit()
cat = conn.execute('SELECT * FROM categories WHERE id=?', (category_id,)).fetchone()
conn.close()
return jsonify(dict(cat)) if cat else jsonify({'error': '未找到'}), 404
@app.route('/api/categories/<category_id>', methods=['DELETE'])
def delete_category(category_id):
conn = get_db()
conn.execute('DELETE FROM categories WHERE id=?', (category_id,))
conn.commit()
conn.close()
return jsonify({'ok': True})
# ==================== 标签API ====================
@app.route('/api/tags', methods=['GET'])
def list_tags():
conn = get_db()
rows = conn.execute(
'SELECT tg.*, (SELECT COUNT(*) FROM topic_tags WHERE tag_id=tg.id) as topic_count '
'FROM tags tg ORDER BY tg.name'
).fetchall()
conn.close()
return jsonify([dict(r) for r in rows])
@app.route('/api/tags', methods=['POST'])
def create_tag():
data = request.get_json(force=True)
name = data.get('name', '').strip()
if not name:
return jsonify({'error': '标签名不能为空'}), 400
conn = get_db()
existing = conn.execute('SELECT * FROM tags WHERE name=?', (name,)).fetchone()
if existing:
conn.close()
return jsonify(dict(existing)) # 已存在直接返回
tid = str(uuid.uuid4())[:8]
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
conn.execute('INSERT INTO tags (id, name, created_at) VALUES (?,?,?)', (tid, name, now))
conn.commit()
tag = conn.execute('SELECT * FROM tags WHERE id=?', (tid,)).fetchone()
conn.close()
return jsonify(dict(tag))
@app.route('/api/tags/<tag_id>', methods=['PUT'])
def update_tag(tag_id):
data = request.get_json(force=True)
conn = get_db()
tag = conn.execute('SELECT * FROM tags WHERE id=?', (tag_id,)).fetchone()
if not tag:
conn.close()
return jsonify({'error': '标签不存在'}), 404
name = data.get('name', '').strip()
if not name:
conn.close()
return jsonify({'error': '标签名不能为空'}), 400
# 检查重名(排除自身)
existing = conn.execute('SELECT id FROM tags WHERE name=? AND id!=?', (name, tag_id)).fetchone()
if existing:
conn.close()
return jsonify({'error': '标签名已存在'}), 400
conn.execute('UPDATE tags SET name=? WHERE id=?', (name, tag_id))
conn.commit()
tag = conn.execute('SELECT * FROM tags WHERE id=?', (tag_id,)).fetchone()
conn.close()
return jsonify(dict(tag))
@app.route('/api/tags/<tag_id>', methods=['DELETE'])
def delete_tag(tag_id):
conn = get_db()
conn.execute('DELETE FROM tags WHERE id=?', (tag_id,))
conn.commit()
conn.close()
return jsonify({'ok': True})
# ==================== 素材API ====================
@app.route('/api/topics/<topic_id>/materials', methods=['POST'])
def add_material(topic_id):
conn = get_db()
# 检查主题存在
if not conn.execute('SELECT id FROM topics WHERE id=?', (topic_id,)).fetchone():
conn.close()
return jsonify({'error': '主题不存在'}), 404
mtype = request.form.get('type', 'text')
mid = str(uuid.uuid4())[:8]
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
if mtype == 'image':
file = request.files.get('file')
if not file or not allowed_file(file.filename):
conn.close()
return jsonify({'error': '不支持的图片格式'}), 400
ext = secure_filename(file.filename).rsplit('.', 1)[-1].lower()
filename = f"{topic_id}_{mid}_{int(time.time())}.{ext}"
filepath = os.path.join(UPLOAD_DIR, filename)
file.save(filepath)
conn.execute(
'INSERT INTO materials (id, topic_id, type, content, file_path, created_at) VALUES (?,?,?,?,?,?)',
(mid, topic_id, 'image', request.form.get('content', ''), f'uploads/{filename}', now)
)
else:
content = request.form.get('content', '').strip()
if not content:
conn.close()
return jsonify({'error': '文本内容不能为空'}), 400
conn.execute(
'INSERT INTO materials (id, topic_id, type, content, created_at) VALUES (?,?,?,?,?)',
(mid, topic_id, 'text', content, now)
)
conn.execute('UPDATE topics SET updated_at=? WHERE id=?', (now, topic_id))
conn.commit()
material = conn.execute('SELECT * FROM materials WHERE id=?', (mid,)).fetchone()
conn.close()
return jsonify(dict(material))
@app.route('/api/materials/<material_id>', methods=['PUT'])
def update_material(material_id):
data = request.get_json(force=True)
conn = get_db()
fields = []
values = []
for key in ['content', 'sort_order']:
if key in data:
fields.append(f'{key}=?')
values.append(data[key])
if fields:
values.append(material_id)
conn.execute(f'UPDATE materials SET {",".join(fields)} WHERE id=?', values)
conn.commit()
material = conn.execute('SELECT * FROM materials WHERE id=?', (material_id,)).fetchone()
conn.close()
return jsonify(dict(material)) if material else jsonify({'error': '未找到'}), 404
@app.route('/api/materials/<material_id>', methods=['DELETE'])
def delete_material(material_id):
conn = get_db()
mat = conn.execute('SELECT * FROM materials WHERE id=?', (material_id,)).fetchone()
if mat and mat['file_path']:
fp = os.path.join(BASE_DIR, 'static', mat['file_path'])
if os.path.exists(fp):
os.remove(fp)
conn.execute('DELETE FROM materials WHERE id=?', (material_id,))
conn.commit()
conn.close()
return jsonify({'ok': True})
# ==================== LLM配置API ====================
@app.route('/api/llm-config', methods=['GET'])
def get_llm_config_api():
config = get_llm_config()
# 隐藏部分API Key
key = config['api_key']
masked = key[:8] + '***' + key[-4:] if len(key) > 12 else '***'
return jsonify({
'url': config['url'],
'api_key_masked': masked,
'api_key_set': bool(key),
'model': config['model']
})
@app.route('/api/llm-config', methods=['PUT'])
def update_llm_config():
data = request.get_json(force=True)
conn = get_db()
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 读取当前配置
current = conn.execute('SELECT * FROM llm_config WHERE id=1').fetchone()
url = data.get('url', current['url'] if current else LLM_URL)
# 如果前端传了空key则保留旧key
api_key = data.get('api_key', '') or (current['api_key'] if current else LLM_API_KEY)
model = data.get('model', current['model'] if current else LLM_MODEL)
conn.execute('''
INSERT INTO llm_config (id, url, api_key, model, updated_at) VALUES (1, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET url=excluded.url, api_key=excluded.api_key, model=excluded.model, updated_at=excluded.updated_at
''', (url, api_key, model, now))
conn.commit()
conn.close()
return jsonify({'ok': True})
# ==================== Prompt历史API ====================
@app.route('/api/topics/<topic_id>/prompt-history', methods=['GET'])
def get_prompt_history(topic_id):
conn = get_db()
history = conn.execute(
'SELECT * FROM prompt_history WHERE topic_id=? ORDER BY created_at DESC LIMIT 20',
(topic_id,)
).fetchall()
conn.close()
return jsonify([dict(h) for h in history])
# ==================== 文章生成API ====================
@app.route('/api/topics/<topic_id>/generate', methods=['POST'])
def generate_article(topic_id):
conn = get_db()
topic = conn.execute('SELECT * FROM topics WHERE id=?', (topic_id,)).fetchone()
if not topic:
conn.close()
return jsonify({'error': '主题不存在'}), 404
materials = conn.execute(
'SELECT * FROM materials WHERE topic_id=? ORDER BY sort_order, created_at',
(topic_id,)
).fetchall()
conn.close()
# 构建素材文本
material_parts = []
for m in materials:
if m['type'] == 'text':
material_parts.append(f"【文本素材】\n{m['content']}")
elif m['type'] == 'image':
desc = m['content'] or '(图片)'
material_parts.append(f"【图片素材】{desc}")
materials_text = '\n\n'.join(material_parts) if material_parts else '(暂无素材)'
# 构建prompt
custom_prompt = topic['prompt'] or '请根据以下素材撰写一篇结构清晰、内容丰富的文章:'
# 获取LLM配置
llm_config = get_llm_config()
llm_url = llm_config['url']
llm_key = llm_config['api_key']
llm_model = llm_config['model']
if not llm_key:
return jsonify({'error': '请先配置LLM API Key首页右上角设置'}), 400
user_message = f"""{custom_prompt}
主题:{topic['name']}
{f'主题说明:{topic["description"]}' if topic['description'] else ''}
=== 素材 ===
{materials_text}
=== 要求 ===
1. 充分利用提供的素材
2. 文章结构完整,层次分明
3. 语言流畅,逻辑清晰
4. 不要输出思考过程,直接给出文章内容"""
# 调用大模型
try:
resp = requests.post(
llm_url,
headers={
'Authorization': f'Bearer {llm_key}',
'Content-Type': 'application/json'
},
json={
'model': llm_model,
'messages': [
{'role': 'user', 'content': user_message}
],
'temperature': 0.7,
'max_tokens': 8192
},
timeout=120
)
resp.raise_for_status()
result = resp.json()
article_content = result['choices'][0]['message']['content']
except Exception as e:
return jsonify({'error': f'生成失败: {str(e)}'}), 500
# 保存prompt历史
phid = str(uuid.uuid4())[:8]
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
conn = get_db()
conn.execute(
'INSERT INTO prompt_history (id, topic_id, prompt, model, created_at) VALUES (?,?,?,?,?)',
(phid, topic_id, custom_prompt, llm_model, now)
)
# 保存文章
aid = str(uuid.uuid4())[:8]
conn.execute(
'INSERT INTO articles (id, topic_id, content, prompt_used, model, llm_url, created_at) VALUES (?,?,?,?,?,?,?)',
(aid, topic_id, article_content, custom_prompt, llm_model, llm_url, now)
)
conn.execute('UPDATE topics SET updated_at=? WHERE id=?', (now, topic_id))
conn.commit()
article = conn.execute('SELECT * FROM articles WHERE id=?', (aid,)).fetchone()
conn.close()
return jsonify(dict(article))
@app.route('/api/articles/<article_id>', methods=['DELETE'])
def delete_article(article_id):
conn = get_db()
conn.execute('DELETE FROM articles WHERE id=?', (article_id,))
conn.commit()
conn.close()
return jsonify({'ok': True})
@app.route('/api/articles/<article_id>', methods=['PUT'])
def update_article(article_id):
data = request.get_json(force=True)
conn = get_db()
conn.execute('UPDATE articles SET content=? WHERE id=?', (data.get('content', ''), article_id))
conn.commit()
conn.close()
return jsonify({'ok': True})
# ==================== 启动 ====================
if __name__ == '__main__':
os.makedirs(UPLOAD_DIR, exist_ok=True)
init_db()
print('=' * 50)
print(' 文章编写系统启动')
print(' 访问地址: http://localhost:19200')
print('=' * 50)
app.run(host='0.0.0.0', port=19200, debug=False)