支持用户注册和登录、发布动态、点赞、评论、私信等功能的社交媒体平台创建!!!

需要整体源代码的可以在我的代码仓下载https://gitcode.com/speaking_me/social-media-platformTest.git

社交媒体平台

描述:社交媒体平台需要支持用户注册、发布动态、点赞、评论、私信等功能。

技术栈

  • 前端:React, Angular, Vue.js
  • 后端:Node.js (Express.js), Python (Django), Ruby on Rails
  • 数据库:MySQL, PostgreSQL, Redis
  • 消息队列:RabbitMQ, Kafka
  • 文件存储:AWS S3, Google Cloud Storage

好的,让我们详细探讨一下社交媒体平台的后端开发。社交媒体平台是一个复杂的系统,涉及到多个方面的技术和架构设计。以下是每个部分的详细说明和建议:

1. 用户注册与登录

功能

  • 用户注册:收集用户的邮箱、用户名、密码等信息。
  • 用户登录:验证用户的身份信息。
  • 用户注销:允许用户安全地注销账户。

技术栈

  • 认证机制:JWT (JSON Web Tokens) 或者 Session
  • 密码加密:使用 bcrypt 或者其他安全的哈希算法
  • 邮件验证:使用第三方邮件服务(如 SendGrid、Mailgun)发送验证邮件

2. 发布动态

功能

  • 用户可以发布文字、图片、视频等动态。
  • 动态可以包含标签、位置信息等元数据。

技术栈

  • 文件上传:使用 Multer (Node.js) 或者 Django 的文件处理模块
  • 文件存储:AWS S3 或 Google Cloud Storage
  • 数据库设计:设计合理的表结构来存储动态信息及其关联数据

3. 点赞与评论

功能

  • 用户可以对动态进行点赞和取消点赞。
  • 用户可以对动态发表评论,并回复其他用户的评论。

技术栈

  • 数据库设计:设计点赞和评论表,确保高效查询和更新
  • 缓存:使用 Redis 缓存热门动态的点赞数和评论数,减少数据库压力

4. 私信功能

功能

  • 用户可以发送私信给其他用户。
  • 用户可以查看收到的私信和发送的私信。

技术栈

  • 消息队列:使用 RabbitMQ 或 Kafka 处理消息的异步传递
  • 数据库设计:设计私信表,记录发送者、接收者、消息内容等信息

5. 数据库设计

表结构示例

  • 用户表 (users)

    • id (主键)
    • username
    • email
    • password_hash
    • created_at
    • updated_at
  • 动态表 (posts)

    • id (主键)
    • user_id (外键,关联用户表)
    • content (动态内容)
    • image_url (图片链接)
    • video_url (视频链接)
    • created_at
    • updated_at
  • 点赞表 (likes)

    • id (主键)
    • post_id (外键,关联动态表)
    • user_id (外键,关联用户表)
    • created_at
  • 评论表 (comments)

    • id (主键)
    • post_id (外键,关联动态表)
    • user_id (外键,关联用户表)
    • content (评论内容)
    • created_at
    • updated_at
  • 私信表 (messages)

    • id (主键)
    • sender_id (外键,关联用户表)
    • receiver_id (外键,关联用户表)
    • content (消息内容)
    • created_at
    • is_read (是否已读)

6. 消息队列

用途

  • 异步处理:处理大量的消息传递,减轻服务器压力。
  • 解耦:将发送消息和接收消息的逻辑分离,提高系统的可扩展性和可靠性。

技术栈

  • RabbitMQ:适用于简单的消息队列需求
  • Kafka:适用于高吞吐量的消息传递和流处理

7. 文件存储

用途

  • 存储用户上传的图片、视频等文件

技术栈

  • AWS S3:提供高可用、高持久性的对象存储服务
  • Google Cloud Storage:类似 AWS S3 的云存储服务

8. 安全性

功能

  • 输入验证:防止 SQL 注入、XSS 攻击等安全问题。
  • 权限管理:确保用户只能访问和操作自己的数据。
  • 数据备份:定期备份数据库,防止数据丢失。

9. 性能优化

技术栈

  • 缓存:使用 Redis 缓存常用数据,减少数据库查询次数。
  • 负载均衡:使用 Nginx 或其他负载均衡器分散请求压力。
  • CDN:使用 CDN 加速静态资源的加载速度。

10. 日志记录

用途

  • 监控系统运行状态:记录系统日志,帮助排查问题。
  • 审计:记录用户操作日志,用于审计和回溯。

技术栈

  • ELK Stack:Elasticsearch、Logstash、Kibana 组成的日志管理解决方案
  • Graylog:另一个强大的日志管理和分析工具

通过以上各个部分的设计和实现,您可以构建一个功能完善的社交媒体平台。

源代码

前端代码

index.html

头部 (<head>)
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>社交媒体平台</title><link rel="stylesheet" href="styles.css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
</head>
  • <meta charset="UTF-8">:设置字符编码为 UTF-8。
  • <meta name="viewport" content="width=device-width, initial-scale=1.0">:设置视口,使页面在移动设备上正确显示。
  • <title>:设置页面标题。
  • <link rel="stylesheet" href="styles.css">:引入自定义的 CSS 文件。
  • <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">:引入 Font Awesome 图标库。
导航栏 (<nav>)
<nav class="navbar"><div class="nav-brand">社交平台</div><div class="nav-menu"><a href="#" class="nav-item">首页</a><a href="#" class="nav-item">消息</a><div class="notification-wrapper"><a href="#" class="nav-item" id="notification-toggle"><i class="fas fa-bell"></i><span class="notification-badge">0</span></a><div class="notification-dropdown hidden"><div class="notification-header"><h3>通知</h3><button class="mark-all-read">全部已读</button></div><div class="notification-list"><!-- 通知内容将动态加载 --></div></div></div><a href="#" class="nav-item">个人中心</a></div>
</nav>
  • <div class="nav-brand">社交平台</div>:显示网站的品牌名称。
  • <a href="#" class="nav-item">首页</a>:首页链接。
  • <a href="#" class="nav-item">消息</a>:消息链接。
  • <div class="notification-wrapper">:通知区域,包含通知图标和未读通知数量。
    • <a href="#" class="nav-item" id="notification-toggle">:点击通知图标会显示通知下拉菜单。
    • <div class="notification-dropdown hidden">:通知下拉菜单,默认隐藏。
      • <div class="notification-header">:通知头部,包含标题和“全部已读”按钮。
      • <div class="notification-list">:通知列表,内容将通过 JavaScript 动态加载。
  • <a href="#" class="nav-item">个人中心</a>:个人中心链接。
主要内容 (<main>)
<main class="container"><div class="sidebar"><div class="profile-card"><img src="avatar.jpg" alt="头像" class="avatar"><h3>用户名</h3><p>个人简介</p></div></div><div class="content"><div class="post-form"><textarea placeholder="分享新动态..."></textarea><div class="post-form-actions"><div class="upload-wrapper"><input type="file" id="image-upload" accept="image/*" multiple class="file-input"><label for="image-upload" class="upload-btn"><i class="fas fa-image"></i> 添加图片</label></div><button class="post-btn">发布</button></div><div class="image-preview"></div></div><div class="posts"><article class="post"><div class="post-header"><img src="avatar.jpg" alt="用户头像" class="post-avatar"><div class="post-info"><h4>用户名</h4><span class="post-time">2小时前</span></div></div><div class="post-content">这是一条示例动态内容</div><div class="post-images"><img src="example1.jpg" alt="发布图片"></div><div class="post-actions"><button class="action-btn"><i class="fas fa-heart"></i> 点赞</button><button class="action-btn comment-toggle"><i class="fas fa-comment"></i> 评论</button><button class="action-btn"><i class="fas fa-share"></i> 分享</button></div><div class="comments-section hidden"><div class="comment-form"><input type="text" placeholder="发表评论..." class="comment-input"><button class="comment-submit">发送</button></div><div class="comments-list"><div class="comment"><img src="avatar.jpg" alt="评论者头像" class="comment-avatar"><div class="comment-content"><div class="comment-header"><span class="comment-author">评论用户</span><span class="comment-time">1小时前</span></div><p class="comment-text">这是一条示例评论</p></div></div></div></div></article></div></div>
</main>
  • <div class="sidebar">:侧边栏,包含用户个人信息卡片。
    • <div class="profile-card">:用户个人信息卡片,包含头像、用户名和个人简介。
  • <div class="content">:主要内容区域,包含动态发布表单和动态列表。
    • <div class="post-form">:动态发布表单。
      • <textarea placeholder="分享新动态..."></textarea>:输入框,用户在此输入动态内容。
      • <div class="post-form-actions">:表单操作按钮。
        • <div class="upload-wrapper">:图片上传区域。
          • <input type="file" id="image-upload" accept="image/*" multiple class="file-input">:文件输入框,用于选择图片文件。
          • <label for="image-upload" class="upload-btn">:文件上传按钮,点击时触发文件输入框。
        • <button class="post-btn">发布</button>:发布按钮,提交表单。
      • <div class="image-preview"></div>:图片预览区域,显示用户选择的图片。
    • <div class="posts">:动态列表,包含一条示例动态。
      • <article class="post">:动态项。
        • <div class="post-header">:动态头部,包含用户头像和信息。
        • <div class="post-content">:动态内容。
        • <div class="post-images">:动态图片。
        • <div class="post-actions">:动态操作按钮,包括点赞、评论和分享。
        • <div class="comments-section hidden">:评论区域,默认隐藏。
          • <div class="comment-form">:评论表单,用户在此输入评论内容。
          • <div class="comments-list">:评论列表,包含一条示例评论。

login.html

头部 (<head>)
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登录 - 社交媒体平台</title><link rel="stylesheet" href="styles.css">
</head>
  • <meta charset="UTF-8">:设置字符编码为 UTF-8。
  • <meta name="viewport" content="width=device-width, initial-scale=1.0">:设置视口,使页面在移动设备上正确显示。
  • <title>登录 - 社交媒体平台</title>:设置页面标题。
  • <link rel="stylesheet" href="styles.css">:引入自定义的 CSS 文件。
身体 (<body>)
<body><div class="auth-container"><div class="auth-box"><h2>登录</h2><form id="loginForm" class="auth-form"><div class="form-group"><label for="email">邮箱</label><input type="email" id="email" required></div><div class="form-group"><label for="password">密码</label><input type="password" id="password" required></div><button type="submit" class="auth-btn">登录</button></form><p class="auth-link">还没有账号?<a href="register.html">立即注册</a></p></div></div><script type="module">import { api } from './api.js';document.getElementById('loginForm').addEventListener('submit', async (e) => {e.preventDefault();try {const email = document.getElementById('email').value;const password = document.getElementById('password').value;await api.login(email, password);window.location.href = '/';} catch (error) {alert(error.message);}});</script>
</body>
  • <div class="auth-container">:登录页面的外层容器,用于居中对齐。
  • <div class="auth-box">:登录表单的容器。
    • <h2>登录</h2>:标题,显示“登录”。
    • <form id="loginForm" class="auth-form">:登录表单。
      • <div class="form-group">:表单组,包含标签和输入框。
        • <label for="email">邮箱</label>:邮箱输入框的标签。
        • <input type="email" id="email" required>:邮箱输入框,类型为 email,必填。
      • <div class="form-group">:表单组,包含标签和输入框。
        • <label for="password">密码</label>:密码输入框的标签。
        • <input type="password" id="password" required>:密码输入框,类型为 password,必填。
      • <button type="submit" class="auth-btn">登录</button>:提交按钮,点击时提交表单。
    • <p class="auth-link">:提示文本,包含注册链接。
      • <a href="register.html">立即注册</a>:注册链接,指向注册页面。

register.html

头部 (<head>)
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>注册 - 社交媒体平台</title><link rel="stylesheet" href="styles.css">
</head>
  • <meta charset="UTF-8">:设置字符编码为 UTF-8。
  • <meta name="viewport" content="width=device-width, initial-scale=1.0">:设置视口,使页面在移动设备上正确显示。
  • <title>注册 - 社交媒体平台</title>:设置页面标题。
  • <link rel="stylesheet" href="styles.css">:引入自定义的 CSS 文件。
身体 (<body>)
<body><div class="auth-container"><div class="auth-box"><h2>注册账号</h2><form id="registerForm" class="auth-form"><div class="form-group"><label for="username">用户名</label><input type="text" id="username" required></div><div class="form-group"><label for="email">邮箱</label><input type="email" id="email" required></div><div class="form-group"><label for="password">密码</label><input type="password" id="password" required></div><button type="submit" class="auth-btn">注册</button></form><p class="auth-link">已有账号?<a href="login.html">立即登录</a></p></div></div><script type="module">import { api } from './api.js';document.getElementById('registerForm').addEventListener('submit', async (e) => {e.preventDefault();try {const username = document.getElementById('username').value;const email = document.getElementById('email').value;const password = document.getElementById('password').value;await api.register(username, email, password);window.location.href = '/';} catch (error) {alert(error.message);}});</script>
</body>
  • <div class="auth-container">:注册页面的外层容器,用于居中对齐。
  • <div class="auth-box">:注册表单的容器。
    • <h2>注册账号</h2>:标题,显示“注册账号”。
    • <form id="registerForm" class="auth-form">:注册表单。
      • <div class="form-group">:表单组,包含标签和输入框。
        • <label for="username">用户名</label>:用户名输入框的标签。
        • <input type="text" id="username" required>:用户名输入框,类型为 text,必填。
      • <div class="form-group">:表单组,包含标签和输入框。
        • <label for="email">邮箱</label>:邮箱输入框的标签。
        • <input type="email" id="email" required>:邮箱输入框,类型为 email,必填。
      • <div class="form-group">:表单组,包含标签和输入框。
        • <label for="password">密码</label>:密码输入框的标签。
        • <input type="password" id="password" required>:密码输入框,类型为 password,必填。
      • <button type="submit" class="auth-btn">注册</button>:提交按钮,点击时提交表单。
    • <p class="auth-link">:提示文本,包含登录链接。
      • <a href="login.html">立即登录</a>:登录链接,指向登录页面。

api.js

1、构造函数 (constructor)
constructor(baseUrl = 'http://localhost:3000/api') {this.baseUrl = baseUrl;this.token = localStorage.getItem('token');
}
  • baseUrl:API 的基础 URL,默认值为 http://localhost:3000/api
  • token:从本地存储中获取当前用户的令牌(如果存在)。
2. 设置和清除令牌的方法
setToken(token) {this.token = token;localStorage.setItem('token', token);
}clearToken() {this.token = null;localStorage.removeItem('token');
}
  • setToken(token):设置当前用户的令牌,并将其保存到本地存储中。
  • clearToken():清除当前用户的令牌,并从本地存储中移除。
3. 发送请求的方法 (request)
async request(endpoint, options = {}) {const headers = {'Content-Type': 'application/json',...options.headers};if (this.token) {headers.Authorization = `Bearer ${this.token}`;}try {const response = await fetch(`${this.baseUrl}${endpoint}`, {...options,headers});if (response.status === 401) {this.clearToken();window.location.href = '/login.html';return;}const data = await response.json();return data;} catch (error) {console.error('API请求失败:', error);throw error;}
}
  • headers:设置请求头,默认包含 Content-Type: application/json,并根据 this.token 添加授权头。
  • fetch:发送 HTTP 请求到指定的 endpoint,合并 options 和 headers
  • response.status === 401:如果响应状态码为 401(未授权),清除令牌并重定向到登录页面。
  • response.json():解析响应数据为 JSON 格式。
  • catch:捕获并处理请求过程中发生的错误。
4. 用户认证相关方法
注册 (register)
async register(username, email, password) {const response = await this.request('/auth/register', {method: 'POST',body: JSON.stringify({ username, email, password })});this.setToken(response.token);return response;
}
  • /auth/register:注册用户。
  • method: 'POST':使用 POST 方法。
  • body: JSON.stringify({ username, email, password }):将用户名、邮箱和密码作为请求体发送。
  • this.setToken(response.token):注册成功后,设置令牌。
登录 (login)
async login(email, password) {const response = await this.request('/auth/login', {method: 'POST',body: JSON.stringify({ email, password })});this.setToken(response.token);return response;
}
  • /auth/login:登录用户。
  • method: 'POST':使用 POST 方法。
  • body: JSON.stringify({ email, password }):将邮箱和密码作为请求体发送。
  • this.setToken(response.token):登录成功后,设置令牌。
注销 (logout)
async logout() {this.clearToken();
}
  • this.clearToken():注销用户,清除令牌。
获取当前用户信息 (getCurrentUser)
async getCurrentUser() {return this.request('/auth/me');
}
  • /auth/me:获取当前用户的详细信息。
5. 帖子相关方法
获取帖子列表 (getPosts)
async getPosts(page = 1) {return this.request(`/posts?page=${page}`);
}
  • /posts?page=${page}:获取指定页数的帖子列表,默认第一页。
发布新帖子 (createPost)
async createPost(content, images = []) {return this.request('/posts', {method: 'POST',body: JSON.stringify({ content, images })});
}
  • /posts:发布新帖子。
  • method: 'POST':使用 POST 方法。
  • body: JSON.stringify({ content, images }):将帖子内容和图片作为请求体发送。
添加评论 (addComment)
async addComment(postId, content) {return this.request(`/posts/${postId}/comments`, {method: 'POST',body: JSON.stringify({ content })});
}
  • /posts/${postId}/comments:向指定帖子添加评论。
  • method: 'POST':使用 POST 方法。
  • body: JSON.stringify({ content }):将评论内容作为请求体发送。
点赞/取消点赞 (toggleLike)
async toggleLike(postId) {return this.request(`/posts/${postId}/like`, {method: 'POST'});
}
  • /posts/${postId}/like:点赞或取消点赞指定帖子。
  • method: 'POST':使用 POST 方法。
6. 导出 API 实例
export const api = new Api();
  • export const api = new Api();:创建一个 Api 类的实例并导出,以便在其他模块中使用。

notifications.js

1. 通知系统 (NotificationSystem)
构造函数 (constructor)
constructor() {this.notifications = [];this.unreadCount = 0;this.setupEventListeners();this.initializeWebSocket();
}
  • this.notifications:存储所有通知的数组。
  • this.unreadCount:未读通知的数量。
  • this.setupEventListeners():设置事件监听器。
  • this.initializeWebSocket():初始化 WebSocket 连接。
设置事件监听器 (setupEventListeners)
setupEventListeners() {const notificationToggle = document.getElementById('notification-toggle');const notificationDropdown = document.querySelector('.notification-dropdown');const markAllRead = document.querySelector('.mark-all-read');notificationToggle.addEventListener('click', (e) => {e.preventDefault();notificationDropdown.classList.toggle('hidden');});markAllRead.addEventListener('click', () => {this.markAllAsRead();});// 点击外部关闭通知下拉框document.addEventListener('click', (e) => {if (!e.target.closest('.notification-wrapper')) {notificationDropdown.classList.add('hidden');}});
}
  • notificationToggle:通知图标按钮。
  • notificationDropdown:通知下拉框。
  • markAllRead:标记所有通知为已读的按钮。
  • notificationToggle.addEventListener('click', ...):点击通知图标时,切换通知下拉框的显示状态。
  • markAllRead.addEventListener('click', ...):点击“标记全部为已读”按钮时,调用 markAllAsRead 方法。
  • document.addEventListener('click', ...):点击页面其他地方时,关闭通知下拉框。
初始化 WebSocket 连接 (initializeWebSocket)
initializeWebSocket() {this.ws = new WebSocket('ws://your-websocket-server');this.ws.onmessage = (event) => {const notification = JSON.parse(event.data);this.addNotification(notification);};
}
  • this.ws = new WebSocket('ws://your-websocket-server'):连接到 WebSocket 服务器。
  • this.ws.onmessage:接收 WebSocket 消息时,解析消息并调用 addNotification 方法。
添加通知 (addNotification)
addNotification(notification) {this.notifications.unshift(notification);this.unreadCount++;this.updateNotificationBadge();this.renderNotification(notification);
}
  • this.notifications.unshift(notification):将新通知添加到通知数组的开头。
  • this.unreadCount++:增加未读通知数量。
  • this.updateNotificationBadge():更新通知徽章。
  • this.renderNotification(notification):渲染新通知。
渲染通知 (renderNotification)
renderNotification(notification) {const notificationList = document.querySelector('.notification-list');const notificationElement = document.createElement('div');notificationElement.className = 'notification-item unread';notificationElement.innerHTML = `<img src="${notification.avatar}" alt="通知头像" class="notification-avatar"><div class="notification-content"><p>${notification.message}</p><span class="notification-time">${this.formatTime(notification.time)}</span></div>`;notificationList.insertBefore(notificationElement, notificationList.firstChild);
}
  • notificationList:通知列表的 DOM 元素。
  • notificationElement:创建一个新的通知项元素。
  • notificationElement.innerHTML:设置通知项的内容。
  • notificationList.insertBefore(notificationElement, notificationList.firstChild):将新通知项插入到通知列表的最前面。
标记所有通知为已读 (markAllAsRead)
markAllAsRead() {this.unreadCount = 0;this.updateNotificationBadge();document.querySelectorAll('.notification-item.unread').forEach(item => {item.classList.remove('unread');});
}
  • this.unreadCount = 0:将未读通知数量设置为 0。
  • this.updateNotificationBadge():更新通知徽章。
  • document.querySelectorAll('.notification-item.unread').forEach(...):移除所有未读通知项的 unread 类。
更新通知徽章 (updateNotificationBadge)
updateNotificationBadge() {const badge = document.querySelector('.notification-badge');badge.textContent = this.unreadCount;badge.style.display = this.unreadCount > 0 ? 'block' : 'none';
}
  • badge:通知徽章的 DOM 元素。
  • badge.textContent = this.unreadCount:设置徽章的文本内容为未读通知数量。
  • badge.style.display = this.unreadCount > 0 ? 'block' : 'none':根据未读通知数量显示或隐藏徽章。
格式化时间 (formatTime)
formatTime(timestamp) {const date = new Date(timestamp);const now = new Date();const diff = now - date;if (diff < 60000) return '刚刚';if (diff < 3600000) return `${Math.floor(diff / 60000)}分钟前`;if (diff < 86400000) return `${Math.floor(diff / 3600000)}小时前`;return `${Math.floor(diff / 86400000)}天前`;
}
  • date:通知的时间戳。
  • now:当前时间。
  • diff:时间差。
  • if (diff < 60000) return '刚刚':如果时间差小于 1 分钟,返回“刚刚”。
  • if (diff < 3600000) return ${Math.floor(diff / 60000)}分钟前`:如果时间差小于 1 小时,返回“多少分钟前”。
  • if (diff < 86400000) return ${Math.floor(diff / 3600000)}小时前`:如果时间差小于 1 天,返回“多少小时前”。
  • return ${Math.floor(diff / 86400000)}天前`:如果时间差大于等于 1 天,返回“多少天前”。
2. 动态加载帖子 (InfiniteScroll)
构造函数 (constructor)
constructor() {this.page = 1;this.loading = false;this.hasMore = true;this.setupScrollListener();
}
  • this.page:当前加载的页数。
  • this.loading:是否正在加载。
  • this.hasMore:是否有更多帖子可以加载。
  • this.setupScrollListener():设置滚动事件监听器。
设置滚动事件监听器 (setupScrollListener)
setupScrollListener() {window.addEventListener('scroll', () => {if (this.loading || !this.hasMore) return;const { scrollTop, scrollHeight, clientHeight } = document.documentElement;if (scrollTop + clientHeight >= scrollHeight - 100) {this.loadMorePosts();}});
}
  • window.addEventListener('scroll', ...):监听窗口滚动事件。
  • if (this.loading || !this.hasMore) return:如果正在加载或没有更多帖子,不执行加载操作。
  • scrollTop + clientHeight >= scrollHeight - 100:当滚动到底部附近时,调用 loadMorePosts 方法。
加载更多帖子 (loadMorePosts)
async loadMorePosts() {this.loading = true;this.showLoader();try {const response = await fetch(`/api/posts?page=${this.page}`);const data = await response.json();if (data.posts.length === 0) {this.hasMore = false;return;}this.renderPosts(data.posts);this.page++;} catch (error) {console.error('加载帖子失败:', error);} finally {this.hideLoader();this.loading = false;}
}
  • this.loading = true:设置加载状态为 true
  • this.showLoader():显示加载指示器。
  • const response = await fetch(/api/posts?page=${this.page}):发送请求获取更多帖子。
  • const data = await response.json():解析响应数据。
  • if (data.posts.length === 0) { this.hasMore = false; return; }:如果没有更多帖子,设置 hasMore 为 false 并返回。
  • this.renderPosts(data.posts):渲染新加载的帖子。
  • this.page++:增加页数。
  • catch (error) { console.error('加载帖子失败:', error); }:捕获并处理请求过程中发生的错误。
  • finally { this.hideLoader(); this.loading = false; }:隐藏加载指示器,设置加载状态为 false
渲染帖子 (renderPosts)
renderPosts(posts) {const postsContainer = document.querySelector('.posts');posts.forEach(post => {const postElement = this.createPostElement(post);postsContainer.appendChild(postElement);});
}
  • postsContainer:帖子容器的 DOM 元素。
  • posts.forEach(post => ...):遍历帖子数组。
  • const postElement = this.createPostElement(post):创建帖子元素。
  • postsContainer.appendChild(postElement):将帖子元素添加到帖子容器中。
创建帖子元素 (createPostElement)
createPostElement(post) {const article = document.createElement('article');article.className = 'post';article.innerHTML = `<div class="post-header"><img src="${post.avatar}" alt="用户头像" class="post-avatar"><div class="post-info"><h4>${post.username}</h4><span class="post-time">${this.formatTime(post.timestamp)}</span></div></div><div class="post-content">${post.content}</div>${post.images ? this.renderImages(post.images) : ''}<div class="post-actions"><button class="action-btn"><i class="far fa-heart"></i> 点赞</button><button class="action-btn comment-toggle"><i class="far fa-comment"></i> 评论</button><button class="action-btn"><i class="far fa-share"></i> 分享</button></div>`;return article;
}
  • article:创建一个新的 article 元素。
  • article.className = 'post':设置帖子类名。
  • article.innerHTML:设置帖子的内容。
  • post.images ? this.renderImages(post.images) : '':如果有图片,调用 renderImages 方法渲染图片。
  • return article:返回创建的帖子元素。
渲染图片 (renderImages)
renderImages(images) {return `<div class="post-images">${images.map(img => `<img src="${img}" alt="发布图片">`).join('')}</div>`;
}
  • images.map(img => <img src="${img}" alt="发布图片">):将图片数组转换为图片元素的字符串数组。
  • .join(''):将字符串数组连接成一个字符串。
  • return ...:返回包含图片的 HTML 字符串。
显示加载指示器 (showLoader)
showLoader() {const loader = document.querySelector('.loading-spinner');loader.classList.remove('hidden');
}
  • loader:加载指示器的 DOM 元素。
  • loader.classList.remove('hidden'):显示加载指示器。
隐藏加载指示器 (hideLoader)
hideLoader() {const loader = document.querySelector('.loading-spinner');loader.classList.add('hidden');
}
  • loader:加载指示器的 DOM 元素。
  • loader.classList.add('hidden'):隐藏加载指示器。
格式化时间 (formatTime)
formatTime(timestamp) {return new NotificationSystem().formatTime(timestamp);
}
  • new NotificationSystem().formatTime(timestamp):使用通知系统的 formatTime 方法格式化时间。
3. 初始化
document.addEventListener('DOMContentLoaded', () => {const notificationSystem = new NotificationSystem();const infiniteScroll = new InfiniteScroll();
});
  • document.addEventListener('DOMContentLoaded', ...):当文档加载完成后,初始化通知系统和动态加载系统。
  • const notificationSystem = new NotificationSystem():创建通知系统的实例。
  • const infiniteScroll = new InfiniteScroll():创建动态加载系统的实例。

script.js

1. 基本设置
变量定义
let currentRotation = 0;
const carousel = document.querySelector('.carousel');
const prevBtn = document.getElementById('prev');
const nextBtn = document.getElementById('next');
  • currentRotation:当前轮播图的旋转角度。
  • carousel:轮播图的 DOM 元素。
  • prevBtn 和 nextBtn:上一个和下一个按钮的 DOM 元素。
2. 手动旋转功能
旋转函数 (rotateCarousel)
function rotateCarousel(direction) {currentRotation += direction * 45;carousel.style.transform = `rotateY(${currentRotation}deg)`;
}
  • direction:旋转方向,1 表示向左旋转,-1 表示向右旋转。
  • currentRotation += direction * 45:根据方向调整旋转角度。
  • carousel.style.transform = rotateY(${currentRotation}deg)``:应用新的旋转角度。
按钮事件监听
prevBtn.addEventListener('click', () => {rotateCarousel(1);
});nextBtn.addEventListener('click', () => {rotateCarousel(-1);
});
  • prevBtn.addEventListener('click', ...):点击上一个按钮时,调用 rotateCarousel(1)
  • nextBtn.addEventListener('click', ...):点击下一个按钮时,调用 rotateCarousel(-1)
3. 触摸滑动支持
触摸事件监听
let touchStartX = 0;
let touchEndX = 0;document.addEventListener('touchstart', (e) => {touchStartX = e.touches[0].clientX;
});document.addEventListener('touchend', (e) => {touchEndX = e.changedTouches[0].clientX;handleSwipe();
});function handleSwipe() {const swipeDistance = touchEndX - touchStartX;if (Math.abs(swipeDistance) > 50) {if (swipeDistance > 0) {rotateCarousel(1);} else {rotateCarousel(-1);}}
}
  • touchStartX 和 touchEndX:记录触摸开始和结束的 X 坐标。
  • document.addEventListener('touchstart', ...):触摸开始时记录初始位置。
  • document.addEventListener('touchend', ...):触摸结束时记录最终位置并调用 handleSwipe
  • handleSwipe:计算滑动距离,如果超过阈值(50 像素),根据滑动方向调用 rotateCarousel
4. 拖动支持
拖动事件监听
let isDragging = false;
let startX;
let startRotation;carousel.addEventListener('mousedown', (e) => {isDragging = true;startX = e.clientX;startRotation = currentRotation;document.addEventListener('mousemove', handleMouseMove);document.addEventListener('mouseup', () => {isDragging = false;document.removeEventListener('mousemove', handleMouseMove);}, { once: true });
});function handleMouseMove(e) {if (!isDragging) return;const deltaX = e.clientX - startX;const sensitivity = 0.5; // 调整灵敏度const newRotation = startRotation + (deltaX * sensitivity);carousel.style.transform = `rotateY(${newRotation}deg)`;currentRotation = newRotation;
}
  • isDragging:是否正在拖动。
  • startX 和 startRotation:记录拖动开始时的 X 坐标和旋转角度。
  • carousel.addEventListener('mousedown', ...):鼠标按下时开始拖动。
  • document.addEventListener('mousemove', handleMouseMove):鼠标移动时调用 handleMouseMove
  • document.addEventListener('mouseup', ...):鼠标释放时结束拖动。
  • handleMouseMove:计算鼠标移动的距离,根据灵敏度调整旋转角度并应用新的旋转角度。
5. 自动旋转功能
自动旋转函数 (autoRotateCarousel)
let autoRotate = true;
const autoRotateSpeed = 0.5;function autoRotateCarousel() {if (autoRotate && !isDragging) {currentRotation -= autoRotateSpeed;carousel.style.transform = `rotateY(${currentRotation}deg)`;try {if (!isMusicPlaying) {bgMusic.play().catch(error => {console.log('自动播放失败:', error);});musicToggle.classList.add('playing');isMusicPlaying = true;}} catch (error) {console.log('音乐播放出错:', error);}}requestAnimationFrame(autoRotateCarousel);
}autoRotateCarousel();
  • autoRotate:是否启用自动旋转。
  • autoRotateSpeed:自动旋转的速度。
  • autoRotateCarousel:自动旋转函数,每帧调用一次。
  • currentRotation -= autoRotateSpeed:减少旋转角度以实现自动旋转。
  • carousel.style.transform = rotateY(${currentRotation}deg)``:应用新的旋转角度。
  • try { ... } catch (error) { ... }:尝试自动播放背景音乐。
  • requestAnimationFrame(autoRotateCarousel):递归调用 autoRotateCarousel 以实现每帧更新。
6. 背景音乐控制
音乐控制函数 (toggleMusic)
const bgMusic = document.getElementById('bgMusic');
const musicToggle = document.getElementById('musicToggle');
let isMusicPlaying = false;function toggleMusic() {if (isMusicPlaying) {bgMusic.pause();musicToggle.classList.remove('playing');} else {bgMusic.play();musicToggle.classList.add('playing');}isMusicPlaying = !isMusicPlaying;
}musicToggle.addEventListener('click', toggleMusic);
  • bgMusic:背景音乐的音频元素。
  • musicToggle:音乐控制按钮的 DOM 元素。
  • isMusicPlaying:是否正在播放音乐。
  • toggleMusic:切换音乐播放状态。
  • musicToggle.addEventListener('click', toggleMusic):点击音乐控制按钮时调用 toggleMusic
7. 鼠标悬停时暂停自动旋转
carousel.addEventListener('mouseenter', () => {autoRotate = false;if (isMusicPlaying) {bgMusic.pause();musicToggle.classList.remove('playing');isMusicPlaying = false;}
});carousel.addEventListener('mouseleave', () => {autoRotate = true;
});
  • carousel.addEventListener('mouseenter', ...):鼠标进入轮播图区域时暂停自动旋转和音乐播放。
  • carousel.addEventListener('mouseleave', ...):鼠标离开轮播图区域时恢复自动旋转。
8. 评论展开/折叠功能
document.querySelectorAll('.comment-toggle').forEach(button => {button.addEventListener('click', function () {const commentsSection = this.closest('.post').querySelector('.comments-section');commentsSection.classList.toggle('hidden');});
});
  • document.querySelectorAll('.comment-toggle').forEach(...):遍历所有评论按钮。
  • button.addEventListener('click', ...):点击评论按钮时,切换评论区域的显示状态。
9. 图片上传和预览
document.getElementById('image-upload').addEventListener('change', function (e) {const preview = document.querySelector('.image-preview');preview.innerHTML = '';[...e.target.files].forEach(file => {if (file.type.startsWith('image/')) {const reader = new FileReader();reader.onload = function (e) {const img = document.createElement('img');img.src = e.target.result;preview.appendChild(img);}reader.readAsDataURL(file);}});
});
  • document.getElementById('image-upload').addEventListener('change', ...):监听文件输入框的变化。
  • preview.innerHTML = '':清空预览区域。
  • [...e.target.files].forEach(...):遍历选择的文件。
  • if (file.type.startsWith('image/')):检查文件类型是否为图片。
  • const reader = new FileReader():创建文件读取器。
  • reader.onload = function (e) { ... }:文件读取完成后,创建图片元素并添加到预览区域。
  • reader.readAsDataURL(file):读取文件为 Data URL。
10. 评论提交
document.querySelectorAll('.comment-form').forEach(form => {form.addEventListener('submit', function (e) {e.preventDefault();const input = this.querySelector('.comment-input');const commentText = input.value.trim();if (commentText) {const commentsList = this.closest('.comments-section').querySelector('.comments-list');const newComment = createCommentElement(commentText);commentsList.insertBefore(newComment, commentsList.firstChild);input.value = '';}});
});
  • document.querySelectorAll('.comment-form').forEach(...):遍历所有评论表单。
  • form.addEventListener('submit', ...):监听表单提交事件。
  • e.preventDefault():阻止表单默认提交行为。
  • const input = this.querySelector('.comment-input'):获取评论输入框。
  • const commentText = input.value.trim():获取并修剪评论内容。
  • if (commentText):如果评论内容不为空,继续处理。
  • const commentsList = this.closest('.comments-section').querySelector('.comments-list'):获取评论列表。
  • const newComment = createCommentElement(commentText):创建新的评论元素。
  • commentsList.insertBefore(newComment, commentsList.firstChild):将新评论插入到评论列表的最前面。
  • input.value = '':清空评论输入框。
11. 创建新评论元素
function createCommentElement(text) {const comment = document.createElement('div');comment.className = 'comment';comment.innerHTML = `<img src="avatar.jpg" alt="评论者头像" class="comment-avatar"><div class="comment-content"><div class="comment-header"><span class="comment-author">当前用户</span><span class="comment-time">刚刚</span></div><p class="comment-text">${text}</p></div>`;return comment;
}
  • const comment = document.createElement('div'):创建一个新的 div 元素。
  • comment.className = 'comment':设置评论类名。
  • comment.innerHTML:设置评论的内容。
  • return comment:返回创建的评论元素。
12. 点赞功能
document.querySelectorAll('.action-btn').forEach(btn => {if (btn.innerHTML.includes('点赞')) {btn.addEventListener('click', function () {const icon = this.querySelector('i');if (icon.classList.contains('fas')) {icon.classList.replace('fas', 'far');this.style.color = '#65676b';} else {icon.classList.replace('far', 'fas');this.style.color = '#1877f2';}});}
});
  • document.querySelectorAll('.action-btn').forEach(...):遍历所有操作按钮。
  • if (btn.innerHTML.includes('点赞')):检查按钮是否为点赞按钮。
  • btn.addEventListener('click', ...):监听按钮点击事件。
  • const icon = this.querySelector('i'):获取按钮内的图标元素。
  • if (icon.classList.contains('fas')):如果图标类名为 fas,表示已点赞,更换为 far 类名并改变颜色。
  • else:如果图标类名为 far,表示未点赞,更换为 fas 类名并改变颜色。

styles.css

1. 基本样式重置
* {margin: 0;padding: 0;box-sizing: border-box;
}
  • *:选择所有元素。
  • margin: 0; padding: 0;:移除所有元素的默认外边距和内边距。
  • box-sizing: border-box;:确保元素的总宽度和高度包括内边距和边框。
2. 页面整体样式
body {font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;background-color: #f0f2f5;
}
  • font-family:设置字体系列。
  • background-color:设置背景颜色。
3. 导航栏样式
.navbar {background-color: #ffffff;padding: 1rem 2rem;box-shadow: 0 2px 4px rgba(0,0,0,0.1);display: flex;justify-content: space-between;align-items: center;position: fixed;width: 100%;top: 0;z-index: 100;
}.nav-brand {font-size: 1.5rem;font-weight: bold;color: #1877f2;
}.nav-menu {display: flex;gap: 1.5rem;
}.nav-item {text-decoration: none;color: #65676b;font-weight: 500;
}
  • .navbar:导航栏的基本样式,包括背景色、内边距、阴影、固定定位等。
  • .nav-brand:品牌名称的样式。
  • .nav-menu:导航菜单的样式,使用 flex 布局。
  • .nav-item:导航项的样式,包括文本装饰、颜色和字体粗细。
4. 容器布局
.container {max-width: 1200px;margin: 80px auto 0;padding: 20px;display: grid;grid-template-columns: 300px 1fr;gap: 20px;
}
  • .container:主内容容器的样式,包括最大宽度、外边距、内边距、网格布局和列间距。
5. 侧边栏样式
.sidebar {position: sticky;top: 100px;
}.profile-card {background: white;padding: 20px;border-radius: 8px;text-align: center;
}.avatar {width: 100px;height: 100px;border-radius: 50%;margin-bottom: 10px;
}
  • .sidebar:侧边栏的样式,使用 sticky 定位。
  • .profile-card:个人资料卡片的样式。
  • .avatar:头像的样式,包括宽度、高度、圆角和底部外边距。
6. 内容区域样式
.content {background: white;border-radius: 8px;padding: 20px;
}.post-form {margin-bottom: 20px;
}.post-form textarea {width: 100%;height: 100px;padding: 10px;border: 1px solid #ddd;border-radius: 8px;resize: none;margin-bottom: 10px;
}.post-btn {background: #1877f2;color: white;border: none;padding: 8px 20px;border-radius: 6px;cursor: pointer;
}.post {border-bottom: 1px solid #ddd;padding: 20px 0;
}.post-header {display: flex;align-items: center;margin-bottom: 10px;
}.post-avatar {width: 40px;height: 40px;border-radius: 50%;margin-right: 10px;
}.post-info h4 {margin-bottom: 4px;
}.post-time {color: #65676b;font-size: 0.9rem;
}.post-content {margin-bottom: 15px;
}.post-actions {display: flex;gap: 15px;
}.action-btn {background: none;border: none;color: #65676b;cursor: pointer;font-size: 0.9rem;
}.action-btn:hover {color: #1877f2;
}
  • .content:内容区域的基本样式。
  • .post-form:帖子表单的样式。
  • .post-form textarea:帖子表单中的文本区域样式。
  • .post-btn:发布按钮的样式。
  • .post:每个帖子的样式。
  • .post-header:帖子头部的样式。
  • .post-avatar:帖子作者头像的样式。
  • .post-info h4:帖子作者信息的样式。
  • .post-time:帖子发布时间的样式。
  • .post-content:帖子内容的样式。
  • .post-actions:帖子操作按钮的样式。
  • .action-btn:操作按钮的样式,包括悬停效果。
7. 响应式设计
@media (max-width: 768px) {.container {grid-template-columns: 1fr;}.sidebar {display: none;}
}
  • @media (max-width: 768px):当屏幕宽度小于等于 768px 时,应用以下样式。
  • .container:容器变为单列布局。
  • .sidebar:隐藏侧边栏。
8. 图片上传相关样式
.post-form-actions {display: flex;justify-content: space-between;align-items: center;margin-bottom: 10px;
}.upload-wrapper {position: relative;
}.file-input {display: none;
}.upload-btn {display: inline-flex;align-items: center;padding: 8px 15px;background: #f0f2f5;border-radius: 6px;cursor: pointer;color: #65676b;
}.upload-btn:hover {background: #e4e6eb;
}.image-preview {display: flex;gap: 10px;flex-wrap: wrap;margin-top: 10px;
}.image-preview img {width: 100px;height: 100px;object-fit: cover;border-radius: 8px;
}
  • .post-form-actions:表单操作区域的样式。
  • .upload-wrapper:上传按钮的容器样式。
  • .file-input:隐藏文件输入框。
  • .upload-btn:上传按钮的样式,包括悬停效果。
  • .image-preview:图片预览区域的样式。
  • .image-preview img:预览图片的样式。
9. 评论区样式
.comments-section {margin-top: 15px;border-top: 1px solid #ddd;padding-top: 15px;
}.comments-section.hidden {display: none;
}.comment-form {display: flex;gap: 10px;margin-bottom: 15px;
}.comment-input {flex: 1;padding: 8px 12px;border: 1px solid #ddd;border-radius: 20px;outline: none;
}.comment-submit {background: #1877f2;color: white;border: none;padding: 8px 15px;border-radius: 20px;cursor: pointer;
}.comment {display: flex;gap: 10px;margin-bottom: 12px;
}.comment-avatar {width: 32px;height: 32px;border-radius: 50%;
}.comment-content {background: #f0f2f5;padding: 8px 12px;border-radius: 12px;flex: 1;
}.comment-header {display: flex;justify-content: space-between;margin-bottom: 4px;
}.comment-author {font-weight: 500;
}.comment-time {color: #65676b;font-size: 0.8rem;
}
  • .comments-section:评论区域的样式。
  • .comments-section.hidden:隐藏评论区域。
  • .comment-form:评论表单的样式。
  • .comment-input:评论输入框的样式。
  • .comment-submit:评论提交按钮的样式。
  • .comment:每个评论的样式。
  • .comment-avatar:评论者头像的样式。
  • .comment-content:评论内容的样式。
  • .comment-header:评论头部的样式。
  • .comment-author:评论者的名称样式。
  • .comment-time:评论时间的样式。
10. 图片展示样式
.post-images {margin: 10px 0;display: grid;grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));gap: 10px;
}.post-images img {width: 100%;border-radius: 8px;object-fit: cover;
}
  • .post-images:帖子图片区域的样式。
  • .post-images img:帖子图片的样式。
11. 加载动画样式
.loading-spinner {position: fixed;top: 0;left: 0;width: 100%;height: 100%;background: rgba(255, 255, 255, 0.8);display: flex;justify-content: center;align-items: center;z-index: 1000;
}.loading-spinner.hidden {display: none;
}.spinner {width: 50px;height: 50px;border: 5px solid #f3f3f3;border-top: 5px solid #1877f2;border-radius: 50%;animation: spin 1s linear infinite;
}@keyframes spin {0% {transform: rotate(0deg);}100% {transform: rotate(360deg);}
}
  • .loading-spinner:加载动画的容器样式。
  • .loading-spinner.hidden:隐藏加载动画。
  • .spinner:加载动画的样式。
  • @keyframes spin:定义加载动画的旋转效果。
12. 通知样式
.notification-wrapper {position: relative;
}.notification-badge {position: absolute;top: -5px;right: -5px;background: #ff4444;color: white;border-radius: 50%;padding: 2px 6px;font-size: 0.7rem;
}.notification-dropdown {position: absolute;top: 100%;right: 0;width: 300px;background: white;border-radius: 8px;box-shadow: 0 2px 10px rgba(0,0,0,0.1);margin-top: 10px;z-index: 1000;
}.notification-dropdown.hidden {display: none;
}.notification-header {padding: 15px;border-bottom: 1px solid #ddd;display: flex;justify-content: space-between;align-items: center;
}.mark-all-read {background: none;border: none;color: #1877f2;cursor: pointer;
}.notification-list {max-height: 400px;overflow-y: auto;
}.notification-item {padding: 12px 15px;border-bottom: 1px solid #f0f2f5;display: flex;align-items: center;gap: 10px;cursor: pointer;
}.notification-item:hover {background: #f0f2f5;
}.notification-item.unread {background: #e7f3ff;
}
  • .notification-wrapper:通知图标容器的样式。
  • .notification-badge:未读通知徽章的样式。
  • .notification-dropdown:通知下拉菜单的样式。
  • .notification-dropdown.hidden:隐藏通知下拉菜单。
  • .notification-header:通知头部的样式。
  • .mark-all-read:标记全部已读按钮的样式。
  • .notification-list:通知列表的样式。
  • .notification-item:每个通知项的样式。
  • .notification-item:hover:通知项悬停效果。
  • .notification-item.unread:未读通知项的样式。
13. 无限滚动加载指示器
.infinite-scroll-loader {text-align: center;padding: 20px;display: none;
}.infinite-scroll-loader.active {display: block;
}
  • .infinite-scroll-loader:无限滚动加载指示器的样式。
  • .infinite-scroll-loader.active:显示加载指示器。
14. 认证页面样式
  • .auth-container:认证页面的容器样式。
  • .auth-box:认证框的样式。
  • .auth-box h2:认证标题的样式。
  • .auth-form:认证表单的样式。
  • .form-group:表单组的样式。
  • .form-group label:表单标签的样式。
  • .form-group input:表单输入框的样式。
  • .auth-btn:认证按钮的样式,包括悬停效果。
  • .auth-link:链接的样式。
  • .auth-link a:链接的样式,包括悬停效果。

后端代码(服务器)

server.js

1. 引入依赖
const express = require('express');
const WebSocket = require('ws');
const cors = require('cors');
const bodyParser = require('body-parser');
  • express:用于构建 Web 服务器。
  • WebSocket:用于实时通信。
  • cors:用于处理跨域请求。
  • bodyParser:用于解析请求体。
2. 初始化 Express 应用
const app = express();
const port = 3000;
  • app:Express 应用实例。
  • port:服务器监听的端口。
3. 配置中间件
app.use(cors());
app.use(bodyParser.json());
app.use(express.static('public'));
  • cors():允许跨域请求。
  • bodyParser.json():解析 JSON 格式的请求体。
  • express.static('public'):提供静态文件服务,路径为 public 目录。
4. 创建 WebSocket 服务器
const wss = new WebSocket.Server({ port: 8080 });const clients = new Set();wss.on('connection', (ws) => {clients.add(ws);ws.on('close', () => {clients.delete(ws);});
});
  • wss:WebSocket 服务器实例。
  • clients:存储所有连接的客户端。
  • wss.on('connection', ...):监听新的 WebSocket 连接,并将客户端添加到 clients 集合中。
  • ws.on('close', ...):监听客户端断开连接,并从 clients 集合中移除。
5. 模拟数据库
let posts = [];
let notifications = [];
  • posts:存储帖子的数据。
  • notifications:存储通知的数据。
6. API 路由
获取帖子列表
app.get('/api/posts', (req, res) => {try {const page = parseInt(req.query.page) || 1;const pageSize = 10;const start = (page - 1) * pageSize;const end = start + pageSize;const paginatedPosts = posts.slice(start, end);res.json({posts: paginatedPosts,hasMore: end < posts.length});} catch (error) {res.status(500).json({ error: '获取帖子失败', details: error.message });}
});
  • /api/posts:GET 请求,用于获取帖子列表。
  • page:当前页码,默认为 1。
  • pageSize:每页显示的帖子数量,默认为 10。
  • start 和 end:计算分页的起始和结束索引。
  • paginatedPosts:分页后的帖子列表。
  • hasMore:是否还有更多帖子。
创建新帖子
app.post('/api/posts', async (req, res) => {try {const { content, images } = req.body;if (!content) {return res.status(400).json({ error: '内容不能为空' });}const newPost = {id: Date.now(),content,images: images || [],username: '当前用户',avatar: 'avatar.jpg',timestamp: new Date(),likes: 0,comments: []};posts.unshift(newPost);const notification = {type: 'new_post',message: `${newPost.username} 发布了新动态`,timestamp: new Date(),avatar: newPost.avatar};broadcastNotification(notification);res.status(201).json(newPost);} catch (error) {res.status(500).json({ error: '发布失败', details: error.message });}
});
  • /api/posts:POST 请求,用于创建新帖子。
  • content 和 images:从请求体中获取帖子内容和图片。
  • newPost:新创建的帖子对象。
  • posts.unshift(newPost):将新帖子添加到帖子列表的开头。
  • broadcastNotification:向所有连接的客户端广播新帖子的通知。
  • res.status(201).json(newPost):返回新创建的帖子。
添加评论
app.post('/api/posts/:postId/comments', (req, res) => {try {const { postId } = req.params;const { content } = req.body;if (!content) {return res.status(400).json({ error: '评论内容不能为空' });}const post = posts.find(p => p.id === parseInt(postId));if (!post) {return res.status(404).json({ error: '帖子不存在' });}const newComment = {id: Date.now(),content,username: '当前用户',avatar: 'avatar.jpg',timestamp: new Date()};post.comments.push(newComment);const notification = {type: 'new_comment',message: `${newComment.username} 评论了你的动态`,timestamp: new Date(),avatar: newComment.avatar};broadcastNotification(notification);res.status(201).json(newComment);} catch (error) {res.status(500).json({ error: '评论失败', details: error.message });}
});
  • /api/posts/:postId/comments:POST 请求,用于添加评论。
  • postId:从 URL 参数中获取帖子 ID。
  • content:从请求体中获取评论内容。
  • post:找到对应的帖子。
  • newComment:新创建的评论对象。
  • post.comments.push(newComment):将新评论添加到帖子的评论列表中。
  • broadcastNotification:向所有连接的客户端广播新评论的通知。
  • res.status(201).json(newComment):返回新创建的评论。
点赞/取消点赞
app.post('/api/posts/:postId/like', (req, res) => {try {const { postId } = req.params;const post = posts.find(p => p.id === parseInt(postId));if (!post) {return res.status(404).json({ error: '帖子不存在' });}post.likes = post.likes + 1;res.json({ likes: post.likes });} catch (error) {res.status(500).json({ error: '操作失败', details: error.message });}
});
  • /api/posts/:postId/like:POST 请求,用于点赞。
  • postId:从 URL 参数中获取帖子 ID。
  • post:找到对应的帖子。
  • post.likes = post.likes + 1:增加帖子的点赞数。
  • res.json({ likes: post.likes }):返回更新后的点赞数。
7. 广播通知
function broadcastNotification(notification) {clients.forEach(client => {if (client.readyState === WebSocket.OPEN) {client.send(JSON.stringify(notification));}});
}
  • broadcastNotification:函数用于向所有连接的客户端广播通知。
  • clients.forEach(client => ...):遍历所有客户端。
  • client.readyState === WebSocket.OPEN:检查客户端是否处于打开状态。
  • client.send(JSON.stringify(notification)):将通知发送给客户端。
8. 错误处理中间件
app.use((err, req, res, next) => {console.error(err.stack);res.status(500).json({error: '服务器错误',details: process.env.NODE_ENV === 'development' ? err.message : '请稍后重试'});
});
  • app.use((err, req, res, next) => ...):全局错误处理中间件。
  • console.error(err.stack):记录错误堆栈。
  • res.status(500).json(...):返回 500 状态码和错误信息。
9. 启动服务器
app.listen(port, () => {console.log(`服务器运行在 http://localhost:${port}`);
});
  • app.listen(port, ...):启动 Express 服务器,监听指定端口。
  • console.log(...):打印服务器启动信息。

auth.js

1. 引入依赖
const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
  • express:用于构建 Web 服务器。
  • express.Router():创建一个路由对象。
  • jwt:用于生成和验证 JSON Web Tokens (JWT)。
  • bcryptjs:用于密码加密和验证。
2. 模拟用户数据库
const users = new Map();
const JWT_SECRET = 'your-secret-key'; // 实际应用中应该使用环境变量
  • users:使用 Map 数据结构来存储用户数据,键为用户 ID,值为用户对象。
  • JWT_SECRET:用于生成和验证 JWT 的密钥,实际应用中应使用环境变量来管理。
3. 注册路由
router.post('/register', async (req, res) => {try {const { username, password, email } = req.body;// 验证输入if (!username || !password || !email) {return res.status(400).json({ error: '所有字段都是必填的' });}// 检查用户是否已存在if (Array.from(users.values()).some(user => user.email === email)) {return res.status(400).json({ error: '该邮箱已被注册' });}// 加密密码const hashedPassword = await bcrypt.hash(password, 10);// 创建新用户const newUser = {id: Date.now().toString(),username,email,password: hashedPassword,avatar: `https://api.multiavatar.com/${username}.png`};users.set(newUser.id, newUser);// 生成 JWTconst token = jwt.sign({ userId: newUser.id }, JWT_SECRET, { expiresIn: '24h' });res.status(201).json({token,user: {id: newUser.id,username: newUser.username,email: newUser.email,avatar: newUser.avatar}});} catch (error) {res.status(500).json({ error: '注册失败', details: error.message });}
});
  • /register:POST 请求,用于用户注册。
  • usernamepassword 和 email:从请求体中获取用户信息。
  • if (!username || !password || !email):验证输入字段是否为空。
  • Array.from(users.values()).some(user => user.email === email):检查邮箱是否已注册。
  • await bcrypt.hash(password, 10):使用 bcrypt 加密密码。
  • newUser:创建新的用户对象。
  • users.set(newUser.id, newUser):将新用户添加到用户数据库中。
  • jwt.sign(...):生成 JWT。
  • res.status(201).json(...):返回注册成功的信息,包括 JWT 和用户信息。
4. 登录路由
router.post('/login', async (req, res) => {try {const { email, password } = req.body;// 查找用户const user = Array.from(users.values()).find(u => u.email === email);if (!user) {return res.status(401).json({ error: '用户名或密码错误' });}// 验证密码const isValidPassword = await bcrypt.compare(password, user.password);if (!isValidPassword) {return res.status(401).json({ error: '用户名或密码错误' });}// 生成 JWTconst token = jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '24h' });res.json({token,user: {id: user.id,username: user.username,email: user.email,avatar: user.avatar}});} catch (error) {res.status(500).json({ error: '登录失败', details: error.message });}
});
  • /login:POST 请求,用于用户登录。
  • email 和 password:从请求体中获取用户信息。
  • Array.from(users.values()).find(u => u.email === email):查找用户。
  • await bcrypt.compare(password, user.password):验证密码。
  • jwt.sign(...):生成 JWT。
  • res.json(...):返回登录成功的信息,包括 JWT 和用户信息。
5. 验证中间件
const authMiddleware = (req, res, next) => {try {const token = req.headers.authorization?.split(' ')[1];if (!token) {return res.status(401).json({ error: '未授权' });}const decoded = jwt.verify(token, JWT_SECRET);req.userId = decoded.userId;next();} catch (error) {res.status(401).json({ error: '无效的令牌' });}
};
  • authMiddleware:验证 JWT 的中间件。
  • req.headers.authorization?.split(' ')[1]:从请求头中提取 JWT。
  • jwt.verify(token, JWT_SECRET):验证 JWT。
  • req.userId = decoded.userId:将用户 ID 添加到请求对象中。
  • next():调用下一个中间件或路由处理器。
6. 获取当前用户信息
router.get('/me', authMiddleware, (req, res) => {const user = users.get(req.userId);if (!user) {return res.status(404).json({ error: '用户不存在' });}res.json({id: user.id,username: user.username,email: user.email,avatar: user.avatar});
});
  • /me:GET 请求,用于获取当前用户信息。
  • authMiddleware:使用验证中间件确保请求已授权。
  • users.get(req.userId):从用户数据库中获取用户信息。
  • res.json(...):返回用户信息。
7. 导出模块
module.exports = { router, authMiddleware };
  • module.exports:导出路由对象和验证中间件,以便在其他文件中使用。

根目录

.env

环境变量配置
PORT=3000
WS_PORT=8080
JWT_SECRET=your-secret-key-here
NODE_ENV=development
变量详解
  1. PORT

    • 3000
    • 用途:定义 HTTP 服务器监听的端口号。在这个例子中,HTTP 服务器将在 3000 端口上运行。
  2. WS_PORT

    • 8080
    • 用途:定义 WebSocket 服务器监听的端口号。在这个例子中,WebSocket 服务器将在 8080 端口上运行。
  3. JWT_SECRET

    • your-secret-key-here
    • 用途:用于生成和验证 JSON Web Tokens (JWT) 的密钥。这个密钥应该是保密的,不应该硬编码在代码中,而应该通过环境变量传递。
  4. NODE_ENV

    • development
    • 用途:定义应用程序的运行环境。常见的值有 development(开发环境)、production(生产环境)和 test(测试环境)。不同的环境可能会有不同的配置和行为

package.json

基本信息
{"name": "social-media-platform","version": "1.0.0","description": "社交媒体平台","main": "server.js"
}
  • name:项目的名称,这里是 social-media-platform
  • version:项目的版本号,这里是 1.0.0
  • description:项目的描述,这里是 社交媒体平台
  • main:项目的入口文件,这里是 server.js
脚本命令
"scripts": {"start": "node server.js","dev": "nodemon server.js"
}
  • start:运行 node server.js 命令启动服务器。
  • dev:运行 nodemon server.js 命令启动服务器,并在文件更改时自动重启服务器。nodemon 是一个开发工具,用于自动重启 Node.js 应用程序。
依赖包
"dependencies": {"express": "^4.17.1","ws": "^8.2.3","cors": "^2.8.5","body-parser": "^1.19.0","jsonwebtoken": "^8.5.1","bcryptjs": "^2.4.3","cookie-parser": "^1.4.5"
}
  • express:一个流行的 Node.js 框架,用于构建 Web 应用程序。
  • ws:一个 WebSocket 库,用于实现实时通信。
  • cors:一个中间件,用于处理跨域请求。
  • body-parser:一个中间件,用于解析请求体。
  • jsonwebtoken:一个库,用于生成和验证 JSON Web Tokens (JWT)。
  • bcryptjs:一个库,用于密码加密和验证。
  • cookie-parser:一个中间件,用于解析 Cookie。
开发依赖包
"devDependencies": {"nodemon": "^2.0.15"
}
  • nodemon:一个开发工具,用于监视 Node.js 应用程序的文件变化并自动重启服务器。

详细图解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/18362.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

跨平台WPF框架Avalonia教程 十五

ListBox 列表框 列表框从元素源集合中显示多行元素&#xff0c;并允许选择单个或多个。 列表中的元素可以组合、绑定和模板化。 列表的高度会扩展以适应所有元素&#xff0c;除非特别设置&#xff08;使用高度属性&#xff09;&#xff0c;或由容器控件设置&#xff0c;例如…

STL之mapset续|红黑树篇

STL之map&set续|红黑树篇 红黑树红黑树的规则红黑树的模拟实现 map&set的模拟实现封装map/set关于红黑树的复用红黑树模板参数set的const迭代器问题 红黑树 红黑树也是一种搜索二叉树&#xff0c;它通过颜色和规则控制树上没有一条路径会比其他路径长两倍&#xff0c;…

三、计算机视觉_03LeNet5及手势识别案例

1 LeNet-5基本介绍 LeNet-5是一种经典的卷积神经网络&#xff08;CNN&#xff09;架构&#xff0c;由Yann LeCun在1998年提出&#xff0c;用于手写数字识别&#xff0c;LeNet-5是卷积神经网络的开创性工作之一&#xff0c;它引入了卷积层、池化层和全连接层的组合&#xff0c;为…

【论文模型复现】深度学习、地质流体识别、交叉学科融合?什么情况,让我们来看看

文献&#xff1a;蓝茜茜,张逸伦,康志宏.基于深度学习的复杂储层流体性质测井识别——以车排子油田某井区为例[J].科学技术与工程,2020,20(29):11923-11930. 本文目录 一、前言二、文献阅读-基于深度学习的复杂储层流体性质测井识别2.1 摘要2.2 当前研究不足2.3 本文创新2.4 论文…

Uni-APP+Vue3+鸿蒙 开发菜鸟流程

参考文档 文档中心 运行和发行 | uni-app官网 AppGallery Connect DCloud开发者中心 环境要求 Vue3jdk 17 Java Downloads | Oracle 中国 【鸿蒙开发工具内置jdk17&#xff0c;本地不使用17会报jdk版本不一致问题】 开发工具 HBuilderDevEco Studio【目前只下载这一个就…

Unity-Editor扩展Odin + 自定义EditorWindow记录

没有上下文&#xff0c;可能你不知道这是什么&#xff08;关于Odin Inspector) 在写一个 Odin 插件的完整文章&#xff0c;卡了三天&#xff0c;之后会放出 使用Unity的人之中 1/10 可能会使用Editor扩展&#xff0c;而这之中的又1/10的 人可能会用Odin这个Editor的附加扩展 -…

FIFO系列 - FIFO使用中需要注意的若干问题

FIFO使用中需要注意的若干问题 文章目录 FIFO使用中需要注意的若干问题前言场景1:包数据FIFO设计之冗余法场景2、FIFO数据传输之流控总结前言 场景1:包数据FIFO设计之冗余法 场景:类似图像、文字等码流数据是不需要重复被访问的,因此使用FIFO进行缓存(如果需要被存储,一…

计算机毕业设计 | springboot+vue大学城水电管理系统 校园学校物业水电管理(附源码+文档)

1&#xff0c;绪论 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理大学城水电管理系统的相关信息成…

5-对象的访问权限

对象的访问权限知识点 对象的分类 在数据库中&#xff0c;数据库的表、索引、视图、缺省值、规则、触发器等等、都可以被称为数据库对象&#xff0c;其中对象主要分为两类 1、模式(schema)对象&#xff1a;模式对象可以理解为一个存储目录、包含视图、索引、数据类型、函数和…

药方新解:Spring Boot中药实验管理系统设计

3系统分析 3.1可行性分析 通过对本中药实验管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本中药实验管理系统采用SSM框架&#xff0c;JAVA作为开发语…

动态规划-完全背包问题——279.完全平方数

1.题目解析 题目来源 279.完全平方数——力扣 测试用例 2.算法原理 1.状态表示 完全背包问题通常都是使用一个二维数组来表示其状态&#xff0c;这里是 dp[i][j]&#xff1a;在[1,i]区间选择平方数&#xff0c;当此时已选择平方数的总和完全等于j时所选择的最小平方数个数 …

二叉树的层序遍历

一、题目 给定一个二叉树&#xff0c;返回该二叉树层序遍历的结果&#xff0c;&#xff08;从左到右&#xff0c;一层一层地遍历&#xff09; 例如&#xff1a; 给定的二叉树是{3,9,20,null,null,15,7}, 该二叉树层序遍历的结果是 [[3],[9,20],[15,7]] 二、解决方案 2.0 树…

模型训练过程的显存占用实测

依赖项说明 pip install nvitop pip install timm pip install peft后续的显存占用数据截图&#xff0c;均基于nvitop命令实现 1、模型显存占用说明 1.1 理论占用值 在 一文讲明白大模型显存占用&#xff08;只考虑单卡&#xff09;与大模型显存占用分析都对模型训练过程中…

后端分层解耦

引入 在上篇所举的例子中&#xff0c;我们将所有的代码均放在HelloControl方法之中&#xff0c;这样会导致代码的复用性、可读性较差&#xff0c;难以维护。因此我们需 三层架构 在之前的代码中&#xff0c;代码大体可以分为三部分&#xff1a;数据访问、数据逻辑处理、响应数…

AIGC 入门全攻略:开启智能创作新时代

一、AIGC 初印象 AIGC&#xff0c;即人工智能生成内容&#xff0c;是继专业生产内容&#xff08;PGC&#xff09;、用户生产内容&#xff08;UGC&#xff09;之后的新型内容创作方式。它涵盖了文本生成、图像与视频创作、音频生成等多个领域&#xff0c;正在以惊人的速度改变着…

约克VRF地暖中央空调,让你舒适过冬

想要冬季过得舒服&#xff0c;采暖必须要到位&#xff01;对于没有集中供暖的南方地区来说&#xff0c;冬季室内阴冷刺骨。 选购地暖中央空调时&#xff0c;强效制热的能力必不可少&#xff0c;让我们可以享受温暖的室内温度&#xff0c;有效减少室内忽冷忽热的温度变化。 约克…

基于Java Springboot宠物领养救助平台

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

使用原生 OpenTelemetry 解锁各种可能性:优先考虑可靠性,而不是专有限制

作者&#xff1a;来自 Elastic Bahubali Shetti•Miguel Luna Elastic 现在支持使用 OTel Operator 在 Kubernetes 上部署和管理 Elastic Distributions of OpenTelemetry (EDOT)。SRE 现在可以访问开箱即用的配置和仪表板&#xff0c;这些配置和仪表板旨在通过 Elastic Observ…

基于python Django的boss直聘数据采集与分析预测系统,爬虫可以在线采集,实时动态显示爬取数据,预测基于技能匹配的预测模型

本系统是基于Python Django框架构建的“Boss直聘”数据采集与分析预测系统&#xff0c;旨在通过技能匹配的方式对招聘信息进行分析与预测&#xff0c;帮助求职者根据自身技能找到最合适的职位&#xff0c;同时为招聘方提供更精准的候选人推荐。系统的核心预测模型基于职位需求技…

安装 python-pcl 遇到的问题

安装python-pcl 成功安装错误尝试尝试一尝试二尝试三 本人环境 Ubuntu 22.04.4LTS ros2-humble cpython 3.0.11 python 3.10.12 libpcl-dev 1.12.1dfsg-3build1 pcl-tools 1.12.1dfsg-3build1 代码摘抄来源&#xff1a;Breadcrumbsouster-ros-extras/scripts/ros2_pcl_filters.…