feat: 导航收藏网站 v1.0
功能: - 网站收藏管理(添加/编辑/删除/收藏) - 分类管理(支持自定义图标和颜色) - 搜索功能 - 点击统计 - 现代暗色主题 UI 技术栈: - 后端: Python Flask + MySQL - 前端: React + Vite + Tailwind CSS - 数据库: MySQL (192.168.8.160) - 端口: 后端 5003, 前端 5173
This commit is contained in:
+291
@@ -0,0 +1,291 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
导航网站后端 API (Flask版本)
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# 添加当前目录到 path
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(BASE_DIR))
|
||||
|
||||
from flask import Flask, request, jsonify, make_response
|
||||
import pymysql
|
||||
from pymysql.cursors import DictCursor
|
||||
|
||||
# 数据库配置
|
||||
DB_HOST = os.environ.get('DB_HOST', '192.168.8.160')
|
||||
DB_PORT = int(os.environ.get('DB_PORT', '3306'))
|
||||
DB_USER = os.environ.get('DB_USER', 'dbuser')
|
||||
DB_PASS = os.environ.get('DB_PASS', 'Tata@1234')
|
||||
DB_NAME = os.environ.get('DB_NAME', 'favorites_nav')
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# 简单 CORS 中间件
|
||||
@app.after_request
|
||||
def add_cors_headers(response):
|
||||
response.headers['Access-Control-Allow-Origin'] = '*'
|
||||
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
|
||||
response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
|
||||
return response
|
||||
|
||||
def get_db_connection():
|
||||
"""获取数据库连接"""
|
||||
return pymysql.connect(
|
||||
host=DB_HOST,
|
||||
port=DB_PORT,
|
||||
user=DB_USER,
|
||||
password=DB_PASS,
|
||||
database=DB_NAME,
|
||||
charset='utf8mb4',
|
||||
cursorclass=DictCursor
|
||||
)
|
||||
|
||||
# ==================== 分类 API ====================
|
||||
|
||||
@app.route('/api/categories', methods=['GET'])
|
||||
def get_categories():
|
||||
"""获取所有分类"""
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute("SELECT * FROM categories ORDER BY sort_order")
|
||||
categories = cursor.fetchall()
|
||||
# 转换日期时间
|
||||
for cat in categories:
|
||||
if cat.get('created_at'):
|
||||
cat['created_at'] = cat['created_at'].isoformat()
|
||||
return jsonify(categories)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@app.route('/api/categories', methods=['POST'])
|
||||
def create_category():
|
||||
"""创建分类"""
|
||||
data = request.get_json()
|
||||
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
sql = "INSERT INTO categories (name, icon, color, sort_order) VALUES (%s, %s, %s, %s)"
|
||||
cursor.execute(sql, (data['name'], data.get('icon', 'folder'), data.get('color', '#6366f1'), data.get('sort_order', 0)))
|
||||
conn.commit()
|
||||
category_id = cursor.lastrowid
|
||||
|
||||
cursor.execute("SELECT * FROM categories WHERE id = %s", (category_id,))
|
||||
category = cursor.fetchone()
|
||||
if category.get('created_at'):
|
||||
category['created_at'] = category['created_at'].isoformat()
|
||||
return jsonify(category)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@app.route('/api/categories/<int:category_id>', methods=['PUT'])
|
||||
def update_category(category_id):
|
||||
"""更新分类"""
|
||||
data = request.get_json()
|
||||
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
sql = "UPDATE categories SET name=%s, icon=%s, color=%s, sort_order=%s WHERE id=%s"
|
||||
cursor.execute(sql, (data['name'], data.get('icon', 'folder'), data.get('color', '#6366f1'), data.get('sort_order', 0), category_id))
|
||||
conn.commit()
|
||||
|
||||
cursor.execute("SELECT * FROM categories WHERE id = %s", (category_id,))
|
||||
category = cursor.fetchone()
|
||||
if category.get('created_at'):
|
||||
category['created_at'] = category['created_at'].isoformat()
|
||||
return jsonify(category)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@app.route('/api/categories/<int:category_id>', methods=['DELETE'])
|
||||
def delete_category(category_id):
|
||||
"""删除分类"""
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute("DELETE FROM categories WHERE id = %s", (category_id,))
|
||||
conn.commit()
|
||||
return jsonify({'message': '删除成功'})
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# ==================== 网站 API ====================
|
||||
|
||||
@app.route('/api/websites', methods=['GET'])
|
||||
def get_websites():
|
||||
"""获取网站列表"""
|
||||
category_id = request.args.get('category_id')
|
||||
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
if category_id:
|
||||
sql = "SELECT * FROM websites WHERE category_id = %s ORDER BY is_favorite DESC, click_count DESC"
|
||||
cursor.execute(sql, (category_id,))
|
||||
else:
|
||||
sql = "SELECT * FROM websites ORDER BY is_favorite DESC, click_count DESC"
|
||||
cursor.execute(sql)
|
||||
|
||||
websites = cursor.fetchall()
|
||||
|
||||
# 获取分类信息
|
||||
cursor.execute("SELECT * FROM categories")
|
||||
categories = {c['id']: c for c in cursor.fetchall()}
|
||||
|
||||
# 转换日期时间并添加分类信息
|
||||
for site in websites:
|
||||
if site.get('created_at'):
|
||||
site['created_at'] = site['created_at'].isoformat()
|
||||
if site.get('updated_at'):
|
||||
site['updated_at'] = site['updated_at'].isoformat()
|
||||
|
||||
cat_id = site.get('category_id')
|
||||
if cat_id and cat_id in categories:
|
||||
cat = categories[cat_id]
|
||||
if cat.get('created_at'):
|
||||
cat['created_at'] = cat['created_at'].isoformat()
|
||||
site['category'] = cat
|
||||
else:
|
||||
site['category'] = None
|
||||
|
||||
return jsonify(websites)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@app.route('/api/websites', methods=['POST'])
|
||||
def create_website():
|
||||
"""创建网站"""
|
||||
data = request.get_json()
|
||||
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
sql = """INSERT INTO websites (name, url, icon, description, category_id, is_favorite)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)"""
|
||||
cursor.execute(sql, (
|
||||
data['name'],
|
||||
data['url'],
|
||||
data.get('icon', ''),
|
||||
data.get('description', ''),
|
||||
data.get('category_id') or None,
|
||||
data.get('is_favorite', False)
|
||||
))
|
||||
conn.commit()
|
||||
website_id = cursor.lastrowid
|
||||
|
||||
cursor.execute("SELECT * FROM websites WHERE id = %s", (website_id,))
|
||||
site = cursor.fetchone()
|
||||
|
||||
if site.get('created_at'):
|
||||
site['created_at'] = site['created_at'].isoformat()
|
||||
if site.get('updated_at'):
|
||||
site['updated_at'] = site['updated_at'].isoformat()
|
||||
site['category'] = None
|
||||
|
||||
return jsonify(site)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@app.route('/api/websites/<int:website_id>', methods=['PUT'])
|
||||
def update_website(website_id):
|
||||
"""更新网站"""
|
||||
data = request.get_json()
|
||||
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
fields = []
|
||||
values = []
|
||||
|
||||
if 'name' in data:
|
||||
fields.append('name = %s')
|
||||
values.append(data['name'])
|
||||
if 'url' in data:
|
||||
fields.append('url = %s')
|
||||
values.append(data['url'])
|
||||
if 'icon' in data:
|
||||
fields.append('icon = %s')
|
||||
values.append(data['icon'])
|
||||
if 'description' in data:
|
||||
fields.append('description = %s')
|
||||
values.append(data['description'])
|
||||
if 'category_id' in data:
|
||||
fields.append('category_id = %s')
|
||||
values.append(data['category_id'] or None)
|
||||
if 'is_favorite' in data:
|
||||
fields.append('is_favorite = %s')
|
||||
values.append(data['is_favorite'])
|
||||
|
||||
if fields:
|
||||
values.append(website_id)
|
||||
sql = f"UPDATE websites SET {', '.join(fields)} WHERE id = %s"
|
||||
cursor.execute(sql, values)
|
||||
conn.commit()
|
||||
|
||||
cursor.execute("SELECT * FROM websites WHERE id = %s", (website_id,))
|
||||
site = cursor.fetchone()
|
||||
|
||||
if site.get('created_at'):
|
||||
site['created_at'] = site['created_at'].isoformat()
|
||||
if site.get('updated_at'):
|
||||
site['updated_at'] = site['updated_at'].isoformat()
|
||||
|
||||
# 获取分类信息
|
||||
if site.get('category_id'):
|
||||
cursor.execute("SELECT * FROM categories WHERE id = %s", (site['category_id'],))
|
||||
cat = cursor.fetchone()
|
||||
if cat and cat.get('created_at'):
|
||||
cat['created_at'] = cat['created_at'].isoformat()
|
||||
site['category'] = cat
|
||||
else:
|
||||
site['category'] = None
|
||||
|
||||
return jsonify(site)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@app.route('/api/websites/<int:website_id>', methods=['DELETE'])
|
||||
def delete_website(website_id):
|
||||
"""删除网站"""
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute("DELETE FROM websites WHERE id = %s", (website_id,))
|
||||
conn.commit()
|
||||
return jsonify({'message': '删除成功'})
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@app.route('/api/websites/<int:website_id>/click', methods=['POST'])
|
||||
def click_website(website_id):
|
||||
"""点击网站"""
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute("UPDATE websites SET click_count = click_count + 1 WHERE id = %s", (website_id,))
|
||||
conn.commit()
|
||||
|
||||
cursor.execute("SELECT click_count FROM websites WHERE id = %s", (website_id,))
|
||||
result = cursor.fetchone()
|
||||
return jsonify({'click_count': result['click_count'] if result else 0})
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# ==================== 健康检查 ====================
|
||||
|
||||
@app.route('/api/health', methods=['GET'])
|
||||
def health_check():
|
||||
return jsonify({'status': 'ok', 'timestamp': datetime.utcnow().isoformat()})
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def root():
|
||||
return jsonify({'message': '导航网站 API', 'docs': '/api/health'})
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5003, debug=False)
|
||||
@@ -0,0 +1,6 @@
|
||||
fastapi==0.109.0
|
||||
uvicorn==0.27.0
|
||||
pymysql==1.1.0
|
||||
sqlalchemy==2.0.25
|
||||
pydantic==2.5.3
|
||||
python-dotenv==1.0.0
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
python3
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
/usr/bin/python3
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
python3
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
lib
|
||||
@@ -0,0 +1,5 @@
|
||||
home = /usr/bin
|
||||
include-system-site-packages = false
|
||||
version = 3.12.3
|
||||
executable = /usr/bin/python3.12
|
||||
command = /usr/bin/python3 -m venv /home/openclaw/.openclaw/workspace/favorites-nav/backend/venv
|
||||
Reference in New Issue
Block a user