diff --git a/README.md b/README.md index 68eaf84..41f3dd6 100644 --- a/README.md +++ b/README.md @@ -30,17 +30,21 @@ pip install -r requirements.txt ``` -### 启动服务 +### 启动主服务 ```bash python backend/app.py ``` -### 访问地址 +访问地址: http://localhost:19004 +### 启动后台管理 + +```bash +python admin/app.py ``` -http://localhost:19004 -``` + +后台地址: http://localhost:19005 ## 项目结构 @@ -102,4 +106,9 @@ tech-forum/ ## License +MIT录 +- 评论回复点赞 + +## License + MIT \ No newline at end of file diff --git a/admin/app.py b/admin/app.py new file mode 100644 index 0000000..969cd6c --- /dev/null +++ b/admin/app.py @@ -0,0 +1,329 @@ +""" +技术论坛 - 后台管理系统 +""" + +from flask import Flask, render_template, jsonify, request +from flask_cors import CORS +import json +from pathlib import Path +from datetime import datetime + +app = Flask(__name__) +CORS(app) + +# 数据目录 +DATA_DIR = Path(__file__).parent.parent / 'data' +USERS_FILE = DATA_DIR / 'users.json' +POSTS_FILE = DATA_DIR / 'posts.json' +TOPICS_FILE = DATA_DIR / 'topics.json' + +def load_users(): + if USERS_FILE.exists(): + return json.loads(USERS_FILE.read_text(encoding='utf-8')) + return {} + +def load_posts(): + if POSTS_FILE.exists(): + return json.loads(POSTS_FILE.read_text(encoding='utf-8')) + return {} + +def load_topics(): + if TOPICS_FILE.exists(): + return json.loads(TOPICS_FILE.read_text(encoding='utf-8')) + return {} + +def save_users(users): + USERS_FILE.write_text(json.dumps(users, ensure_ascii=False, indent=2), encoding='utf-8') + +def save_posts(posts): + POSTS_FILE.write_text(json.dumps(posts, ensure_ascii=False, indent=2), encoding='utf-8') + +def save_topics(topics): + TOPICS_FILE.write_text(json.dumps(topics, ensure_ascii=False, indent=2), encoding='utf-8') + +# ============ 页面路由 ============ + +@app.route('/') +def index(): + return render_template('index.html') + +@app.route('/users') +def users_page(): + return render_template('users.html') + +@app.route('/posts') +def posts_page(): + return render_template('posts.html') + +@app.route('/topics') +def topics_page(): + return render_template('topics.html') + +# ============ API路由 ============ + +@app.route('/api/stats') +def api_stats(): + users = load_users() + posts = load_posts() + topics = load_topics() + + # 统计 + total_messages = 0 + for post in posts.values(): + total_messages += len(post.get('replies', [])) + + today = datetime.now().strftime('%Y-%m-%d') + today_posts = sum(1 for p in posts.values() if p.get('created_at', '').startswith(today)) + today_users = sum(1 for u in users.values() if u.get('created_at', '').startswith(today)) + + # 帖子类型统计 + discussion_count = sum(1 for p in posts.values() if p.get('type') == 'discussion') + share_count = sum(1 for p in posts.values() if p.get('type') == 'share') + + return jsonify({ + 'users_count': len(users), + 'posts_count': len(posts), + 'topics_count': len(topics), + 'messages_count': total_messages, + 'today_posts': today_posts, + 'today_users': today_users, + 'discussion_count': discussion_count, + 'share_count': share_count, + }) + +@app.route('/api/users') +def api_users(): + users = load_users() + posts = load_posts() + + user_list = [] + for uid, user in users.items(): + # 统计用户帖子和回复数 + posts_count = len(user.get('posts', [])) + replies_count = 0 + for post in posts.values(): + for reply in post.get('replies', []): + if reply.get('author_id') == uid: + replies_count += 1 + + user_list.append({ + 'id': uid, + 'username': user.get('username', ''), + 'email': user.get('email', ''), + 'phone': user.get('phone', ''), + 'posts_count': posts_count, + 'replies_count': replies_count, + 'created_at': user.get('created_at', ''), + }) + + user_list.sort(key=lambda x: x['created_at'], reverse=True) + return jsonify(user_list) + +@app.route('/api/users/', methods=['DELETE']) +def api_delete_user(user_id): + users = load_users() + posts = load_posts() + topics = load_topics() + + if user_id not in users: + return jsonify({'error': '用户不存在'}), 404 + + # 删除用户的帖子 + for post_id in users[user_id].get('posts', []): + if post_id in posts: + del posts[post_id] + + # 从主题关注中移除 + for topic in topics.values(): + if user_id in topic.get('followers', []): + topic['followers'].remove(user_id) + + # 删除用户 + del users[user_id] + + save_users(users) + save_posts(posts) + save_topics(topics) + + return jsonify({'success': True}) + +@app.route('/api/posts') +def api_posts(): + posts = load_posts() + users = load_users() + + post_type = request.args.get('type') + + post_list = [] + for pid, post in posts.items(): + if post_type and post['type'] != post_type: + continue + + author = users.get(post['author_id'], {}) + post_list.append({ + 'id': pid, + 'title': post['title'], + 'type': post['type'], + 'author': author.get('username', '未知'), + 'author_id': post['author_id'], + 'likes': len(post.get('likes', [])), + 'replies': len(post.get('replies', [])), + 'views': post.get('views', 0), + 'is_pinned': post.get('is_pinned', False), + 'created_at': post['created_at'], + }) + + post_list.sort(key=lambda x: x['created_at'], reverse=True) + return jsonify(post_list) + +@app.route('/api/posts/') +def api_post_detail(post_id): + posts = load_posts() + users = load_users() + + post = posts.get(post_id) + if not post: + return jsonify({'error': '帖子不存在'}), 404 + + author = users.get(post['author_id'], {}) + + # 获取回复 + replies = [] + for reply in post.get('replies', []): + reply_author = users.get(reply['author_id'], {}) + replies.append({ + 'id': reply['id'], + 'content': reply['content'][:100] + '...' if len(reply['content']) > 100 else reply['content'], + 'author': reply_author.get('username', '未知'), + 'likes': len(reply.get('likes', [])), + 'created_at': reply['created_at'], + }) + + return jsonify({ + 'id': post_id, + 'title': post['title'], + 'content': post['content'], + 'type': post['type'], + 'author': author.get('username', '未知'), + 'tags': post.get('tags', []), + 'likes': len(post.get('likes', [])), + 'replies': replies, + 'views': post.get('views', 0), + 'is_pinned': post.get('is_pinned', False), + 'created_at': post['created_at'], + }) + +@app.route('/api/posts/', methods=['DELETE']) +def api_delete_post(post_id): + posts = load_posts() + users = load_users() + + if post_id not in posts: + return jsonify({'error': '帖子不存在'}), 404 + + author_id = posts[post_id].get('author_id') + + del posts[post_id] + + # 从用户列表中移除 + if author_id and author_id in users: + if post_id in users[author_id].get('posts', []): + users[author_id]['posts'].remove(post_id) + + save_posts(posts) + save_users(users) + + return jsonify({'success': True}) + +@app.route('/api/posts//pin', methods=['POST']) +def api_pin_post(post_id): + posts = load_posts() + + if post_id not in posts: + return jsonify({'error': '帖子不存在'}), 404 + + posts[post_id]['is_pinned'] = not posts[post_id].get('is_pinned', False) + save_posts(posts) + + return jsonify({ + 'success': True, + 'is_pinned': posts[post_id]['is_pinned'] + }) + +@app.route('/api/topics') +def api_topics(): + topics = load_topics() + users = load_users() + + topic_list = [] + for tid, topic in topics.items(): + author = users.get(topic['author_id'], {}) + topic_list.append({ + 'id': tid, + 'name': topic['name'], + 'icon': topic.get('icon', '🔧'), + 'author': author.get('username', '未知'), + 'sub_topics_count': len(topic.get('sub_topics', [])), + 'questions_count': len(topic.get('questions', [])), + 'followers_count': len(topic.get('followers', [])), + 'created_at': topic['created_at'], + }) + + topic_list.sort(key=lambda x: x['followers_count'], reverse=True) + return jsonify(topic_list) + +@app.route('/api/topics/') +def api_topic_detail(topic_id): + topics = load_topics() + users = load_users() + + topic = topics.get(topic_id) + if not topic: + return jsonify({'error': '主题不存在'}), 404 + + author = users.get(topic['author_id'], {}) + + return jsonify({ + 'id': topic_id, + 'name': topic['name'], + 'description': topic.get('description', ''), + 'icon': topic.get('icon', '🔧'), + 'author': author.get('username', '未知'), + 'sub_topics': topic.get('sub_topics', []), + 'questions': topic.get('questions', []), + 'followers_count': len(topic.get('followers', [])), + 'created_at': topic['created_at'], + }) + +@app.route('/api/topics/', methods=['DELETE']) +def api_delete_topic(topic_id): + topics = load_topics() + + if topic_id not in topics: + return jsonify({'error': '主题不存在'}), 404 + + del topics[topic_id] + save_topics(topics) + + return jsonify({'success': True}) + +@app.route('/api/tags') +def api_tags(): + posts = load_posts() + + tag_counts = {} + for post in posts.values(): + for tag in post.get('tags', []): + tag_counts[tag] = tag_counts.get(tag, 0) + 1 + + tags = sorted(tag_counts.items(), key=lambda x: x[1], reverse=True) + return jsonify([{'name': t[0], 'count': t[1]} for t in tags[:20]]) + +if __name__ == '__main__': + print("=" * 50) + print("技术论坛 - 后台管理系统") + print("=" * 50) + print(f"访问地址: http://localhost:19005") + print("=" * 50) + + app.run(host='0.0.0.0', port=19005, debug=True) \ No newline at end of file diff --git a/admin/templates/index.html b/admin/templates/index.html new file mode 100644 index 0000000..953a882 --- /dev/null +++ b/admin/templates/index.html @@ -0,0 +1,206 @@ + + + + + + 技术论坛 - 后台管理 + + + + + +
+ + + + +
+ +
+
+
+
+

用户总数

+

-

+
+
+ +
+
+

今日新增: 0

+
+ +
+
+
+

帖子总数

+

-

+
+
+ +
+
+

今日发布: 0

+
+ +
+
+
+

主题总数

+

-

+
+
+ +
+
+
+ +
+
+
+

消息总数

+

-

+
+
+ +
+
+
+
+ + +
+
+

+ + 帖子类型分布 +

+
+
+
0
+
技术交流
+
+
+
0
+
工具分享
+
+
+
+ +
+

+ + 热门标签 +

+
+

加载中...

+
+
+
+ + +
+

+ + 最新帖子 +

+
+

加载中...

+
+
+
+
+ + + + \ No newline at end of file diff --git a/admin/templates/posts.html b/admin/templates/posts.html new file mode 100644 index 0000000..7201b24 --- /dev/null +++ b/admin/templates/posts.html @@ -0,0 +1,203 @@ + + + + + + 帖子管理 - 论坛后台 + + + + +
+ + +
+
+

帖子管理

+ +
+ +
+ + + + + + + + + + + + + + + +
标题类型作者浏览/赞/回复状态发布时间操作
加载中...
+
+ + + +
+
+ + + + \ No newline at end of file diff --git a/admin/templates/topics.html b/admin/templates/topics.html new file mode 100644 index 0000000..d055797 --- /dev/null +++ b/admin/templates/topics.html @@ -0,0 +1,181 @@ + + + + + + 主题管理 - 论坛后台 + + + + +
+ + +
+

主题管理

+ +
+ + + + + + + + + + + + + + + +
主题创建者子主题问题关注创建时间操作
加载中...
+
+ + + +
+
+ + + + \ No newline at end of file diff --git a/admin/templates/users.html b/admin/templates/users.html new file mode 100644 index 0000000..df2a2c1 --- /dev/null +++ b/admin/templates/users.html @@ -0,0 +1,110 @@ + + + + + + 用户管理 - 论坛后台 + + + + +
+ + +
+

用户管理

+ +
+ + + + + + + + + + + + + + + +
用户名邮箱手机帖子数回复数注册时间操作
加载中...
+
+
+
+ + + + \ No newline at end of file