Project Templates
Social Network
如何创建社交媒体平台:一步步指南(2025)
66 分
介绍 在本教程中,您将学习如何使用 back4app 作为后端服务构建类似 instagram 的社交网络平台。back4app 提供了一个托管的 parse server 后端,简化了用户身份验证、数据存储、文件上传和实时功能,而无需复杂的服务器基础设施。 通过完成本教程,您将构建一个完整的社交网络,包含: 用户身份验证(注册、登录、密码重置) 个人资料管理 带图像上传的帖子创建 社交互动(点赞、评论) 带输入指示的实时消息 内容搜索功能 社交网络预览聊天界面用户资料信息流页面 在任何时候,您都可以访问完整的代码,位于 github 先决条件 要完成本教程,您需要 一个 back4app 账户 在 back4app com https //www back4app com/ 注册一个免费账户 在您的本地计算机上安装 node js 和 npm 从 nodejs org https //nodejs org/ 安装 node js(版本 14 x 或更高) 对 javascript 和 react 的基本理解 代码编辑器 任何现代代码编辑器,如 visual studio code 或 sublime text 步骤 1 — 设置您的 back4app 后端 首先,让我们创建一个新的 back4app 项目并设置我们的社交网络的数据库架构。 创建一个新的 back4app 项目 登录到您的 back4app 账户并导航到仪表板 点击 "创建一个新应用" 输入 "back4gram" 作为您的应用名称,选择最近的服务器区域,然后点击 "创建" 理解数据库架构 我们的社交网络需要在 back4app 中创建以下类: 用户 (在 parse 中默认已存在) 将扩展附加字段,如个人简介和头像 帖子 存储用户帖子,包括文本内容和图像 评论 存储对帖子的评论 对话 表示用户之间的聊天对话 消息 对话中的单个消息 输入状态 跟踪用户在对话中输入时的状态 创建数据库类 让我们在您的 back4app 数据库中创建这些类: 在您的 back4app 控制面板中导航到 "数据库" 部分 扩展用户类 点击已经存在的"用户"类 添加以下列: 生物 (类型 字符串) 头像 (类型 文件) 关注者(类型:数字,默认:0) 以下 (类型 数字, 默认 0) 创建 post 类 点击 "创建一个班级" 输入"post"作为类名并选择"创建一个空类" 添加以下列: 内容(类型:字符串) 作者(类型:指向 user 的指针) 图像(类型:文件) 喜欢 (类型 数字, 默认 0) 被喜欢 (类型 数组) 创建于(类型:日期,自动添加) 创建评论类 创建一个名为 "comment" 的新类,包含以下列: 内容(类型:字符串) 作者(类型:指向 user 的指针) 帖子(类型:指向帖子的指针) 创建于(类型:日期,自动添加) 创建对话类 创建一个名为 "conversation" 的新类,包含以下列: 参与者(类型:数组) 最后消息 (类型 字符串) 更新于(类型:日期,自动添加) 创建消息类 创建一个名为 "message" 的新类,包含以下列: 文本(类型:字符串) 发送者(类型:指向 user 的指针) 对话(类型:指向对话的指针) 创建于(类型:日期,自动添加) 创建 typingstatus 类 创建一个名为 "typingstatus" 的新类,包含以下列: 用户(类型:指向 user 的指针) 对话(类型:指向对话的指针) 正在输入 (类型 布尔值) 设置班级权限 为了保护您的应用程序数据,请为每个类配置适当的访问控制列表(acl) 在您的 back4app 控制面板中导航到 "安全性与密钥" 部分 在 "类级别安全性" 下,为每个类配置权限: 例如,在 帖子类 : 公共读取访问:启用(每个人都可以查看帖子) 公共写入访问:启用(经过身份验证的用户可以创建帖子) 添加 clp 以限制更新/删除仅限于作者 设置 livequery 以实现实时功能 要启用实时功能,如消息和输入指示器: 在您的 back4app 控制面板中导航到 "服务器设置" 在 "parse 服务器" 下,找到 "livequery" 并启用它 添加这些类以便通过 livequery 进行监控: 消息 输入状态 帖子(用于实时更新点赞和评论) 获取您的应用程序密钥 您需要您的 back4app 应用程序密钥以连接您的前端: 导航到 "应用设置" > "安全性与密钥" 记下以下密钥: 应用程序 id javascript 密钥 服务器 url livequery 服务器 url 安全密钥 步骤 2 — 将您的前端连接到 back4app 让我们设置您的前端与 back4app 后端之间的连接。 创建环境变量 在您的项目根目录中创建一个 env local 文件以存储您的 back4app 凭据: react app parse app id=your application id react app parse js key=your javascript key react app parse server url=https //parseapi back4app com react app parse live query url=wss\ //your app id back4app io 用您的实际 back4app 凭据替换占位符值。 使用 back4app 配置 parse sdk 创建一个配置文件以使用您的 back4app 凭据初始化 parse: // src/utils/parseconfig js import parse from 'parse/dist/parse min js'; // initialize parse parse initialize( process env react app parse app id, process env react app parse js key ); parse serverurl = process env react app parse server url; // initialize live queries if (process env react app parse live query url) { parse livequeryserverurl = process env react app parse live query url; } export default parse; 然后在您应用程序的入口点导入此配置: // src/index js import react from 'react'; import reactdom from 'react dom/client'; import ' /index css'; import app from ' /app'; import ' /utils/parseconfig'; // import parse configuration const root = reactdom createroot(document getelementbyid('root')); root render( \<react strictmode> \<app /> \</react strictmode> ); 步骤 3 — 使用 back4app 实现身份验证 back4app 的 parse 服务器通过 parse user 类提供全面的用户管理系统。 理解 parse 用户身份验证 parse user 类专门用于用户管理: 存储用户凭据(用户名、电子邮件、密码) 管理身份验证状态 自动处理会话令牌 实现用户注册 以下是如何使用 parse 实现用户注册: // signup function const handlesignup = async () => { try { // create a new user const user = new parse user(); user set('username', username); user set('email', email); user set('password', password); // additional user data user set('bio', ''); user set('followers', 0); user set('following', 0); // sign up the user await user signup(); // success user is automatically logged in console log('user registered successfully'); // navigate to feed or home page navigate('/feed'); } catch (error) { // handle specific parse errors console error('error signing up ', error message); if (error code === 202) { seterrors({ errors, username 'username already taken'}); } else if (error code === 203) { seterrors({ errors, email 'email already in use'}); } } }; 实现用户登录 以下是如何使用 parse 实现用户登录 // login function const handlelogin = async () => { try { // log in the user const user = await parse user login(username, password); // success user is logged in console log('user logged in successfully ', user getusername()); // navigate to feed or home page navigate('/feed'); } catch (error) { // handle login errors console error('error logging in ', error message); setloginerror(error message); } }; 检查当前用户 parse 自动存储会话令牌,允许您检查用户是否已登录 // check if user is already logged in const checkcurrentuser = async () => { try { const currentuser = await parse user current(); if (currentuser) { console log('current user ', currentuser getusername()); return currentuser; } return null; } catch (error) { console error('error checking current user ', error); return null; } }; 实现密码重置 back4app 提供了内置的密码重置流程: // password reset function const handlepasswordreset = async () => { try { await parse user requestpasswordreset(email); // success email sent to user console log('password reset email sent'); setresetemailsent(true); } catch (error) { // handle reset errors console error('error requesting password reset ', error message); setreseterror(error message); } }; 步骤 4 — 创建和显示帖子 现在让我们使用 back4app 实现帖子创建和检索。 创建帖子 以下是如何使用 parse 创建新帖子 // create post function const createpost = async () => { if (!postcontent trim() && !postimage) { console error('post must have content or image'); return; } try { // create a new post object const post = parse object extend('post'); const newpost = new post(); // set post content newpost set('content', postcontent); newpost set('author', parse user current()); newpost set('likes', 0); newpost set('likedby', \[]); // if there's an image, upload it if (postimage) { const parsefile = new parse file(postimage name, postimage); await parsefile save(); newpost set('image', parsefile); } // save the post await newpost save(); console log('post created successfully'); return newpost; } catch (error) { console error('error creating post ', error message); throw error; } }; back4app 的关键机制 parse object extend('post') 引用 back4app 中的 post 类 new post() 创建 post 类的新实例 parsefile save() 将文件上传到 back4app 的存储 newpost save() 将帖子对象保存到 back4app 获取帖子 以下是如何从 back4app 获取帖子 // fetch posts function const fetchposts = async (page = 0, limit = 10) => { try { // create a query for the post class const post = parse object extend('post'); const query = new parse query(post); // include the author object (pointer) query include('author'); // sort by creation date, newest first query descending('createdat'); // pagination query limit(limit); query skip(page limit); // execute the query const results = await query find(); // process the results const posts = results map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, likedby post get('likedby') || \[], createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); return posts; } catch (error) { console error('error fetching posts ', error); throw error; } }; back4app 关键机制: new parse query(post) 在 post 类上创建查询 query include('author') 执行类似连接的操作以获取相关对象 query descending('createdat') 按创建日期对结果进行排序 query limit() 和 query skip() 实现分页 post get('image') url() 获取 parse 文件对象的 url 实现点赞功能 以下是如何实现帖子点赞的步骤: // like/unlike post function const togglelike = async (postid) => { try { const currentuser = parse user current(); const userid = currentuser id; // get the post object const post = parse object extend('post'); const query = new parse query(post); const post = await query get(postid); // get current likes and likedby array const likedby = post get('likedby') || \[]; const isliked = likedby includes(userid); // update likes count and likedby array if (isliked) { // unlike remove user from likedby and decrement likes post set('likedby', likedby filter(id => id !== userid)); post set('likes', math max((post get('likes') || 1) 1, 0)); } else { // like add user to likedby and increment likes post set('likedby', \[ likedby, userid]); post set('likes', (post get('likes') || 0) + 1); } // save the updated post await post save(); return !isliked; // return new like status } catch (error) { console error('error toggling like ', error); throw error; } }; 步骤 5 — 在帖子上实现评论功能 让我们使用 back4app 实现评论功能。 创建评论 以下是如何向帖子添加评论的方法: // add comment function const addcomment = async (postid, commentcontent) => { if (!commentcontent trim()) { console error('comment cannot be empty'); return; } try { // get the post object const post = parse object extend('post'); const postquery = new parse query(post); const post = await postquery get(postid); // create a new comment object const comment = parse object extend('comment'); const comment = new comment(); // set comment data comment set('content', commentcontent); comment set('author', parse user current()); comment set('post', post); // save the comment await comment save(); console log('comment added successfully'); return comment; } catch (error) { console error('error adding comment ', error); throw error; } }; 获取帖子的评论 以下是如何获取特定帖子的评论的方法: // fetch comments function const fetchcomments = async (postid) => { try { // get the post object const post = parse object extend('post'); const postquery = new parse query(post); const post = await postquery get(postid); // create a query for comments const comment = parse object extend('comment'); const query = new parse query(comment); // find comments for this post query equalto('post', post); // include the author information query include('author'); // sort by creation date query ascending('createdat'); // execute the query const results = await query find(); // process the results const comments = results map(comment => ({ id comment id, content comment get('content'), createdat comment get('createdat'), author { id comment get('author') id, username comment get('author') get('username'), avatar comment get('author') get('avatar') ? comment get('author') get('avatar') url() null } })); return comments; } catch (error) { console error('error fetching comments ', error); throw error; } }; 步骤 6 — 实现用户个人资料 现在让我们使用 back4app 实现用户个人资料。 获取用户数据 以下是获取用户个人资料数据的方法: // fetch user profile function const fetchuserprofile = async (userid) => { try { // create a query for the user class const query = new parse query(parse user); // get the user by id const user = await query get(userid); // get user posts const post = parse object extend('post'); const postsquery = new parse query(post); postsquery equalto('author', user); postsquery include('author'); postsquery descending('createdat'); const posts = await postsquery find(); // process user data const userdata = { id user id, username user get('username'), bio user get('bio') || '', avatar user get('avatar') ? user get('avatar') url() null, followers user get('followers') || 0, following user get('following') || 0, posts posts map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat') })) }; return userdata; } catch (error) { console error('error fetching user profile ', error); throw error; } }; 更新用户个人资料 以下是如何更新用户资料: // update user profile function const updateuserprofile = async (profiledata) => { try { const currentuser = await parse user current(); // update profile fields if (profiledata bio !== undefined) { currentuser set('bio', profiledata bio); } if (profiledata avatarfile) { const parsefile = new parse file('avatar jpg', profiledata avatarfile); await parsefile save(); currentuser set('avatar', parsefile); } // save the updated user await currentuser save(); console log('profile updated successfully'); return currentuser; } catch (error) { console error('error updating profile ', error); throw error; } }; 步骤 7 — 使用 livequery 实现实时消息传递 现在让我们使用 back4app 的 livequery 功能实现实时消息传递。 创建对话 以下是如何创建新对话: // create conversation function const createconversation = async (participantids) => { try { // ensure current user is included in participants const currentuser = await parse user current(); const allparticipantids = \[ new set(\[currentuser id, participantids])]; // check if conversation already exists const existingconversation = await findexistingconversation(allparticipantids); if (existingconversation) { return existingconversation; } // get user objects for all participants const participantpointers = await promise all( allparticipantids map(async (id) => { const userquery = new parse query(parse user); return await userquery get(id); }) ); // create new conversation const conversation = parse object extend('conversation'); const conversation = new conversation(); conversation set('participants', participantpointers); conversation set('lastmessage', ''); await conversation save(); console log('conversation created successfully'); return conversation; } catch (error) { console error('error creating conversation ', error); throw error; } }; // helper to find existing conversation const findexistingconversation = async (participantids) => { try { const conversation = parse object extend('conversation'); const query = new parse query(conversation); // this is a simplified approach in production you'd need more complex query const results = await query find(); for (const conversation of results) { const participants = conversation get('participants') || \[]; const participantids = participants map(p => p id); // check if arrays have same elements (regardless of order) if (participantids length === participantids length && participantids every(id => participantids includes(id))) { return conversation; } } return null; } catch (error) { console error('error finding existing conversation ', error); return null; } }; 发送消息 以下是如何在对话中发送消息: // send message function const sendmessage = async (conversationid, messagetext) => { try { const currentuser = parse user current(); // get the conversation const conversation = parse object extend('conversation'); const conversationquery = new parse query(conversation); const conversation = await conversationquery get(conversationid); // create a new message const message = parse object extend('message'); const message = new message(); message set('text', messagetext); message set('sender', currentuser); message set('conversation', conversation); message set('read', false); await message save(); // update conversation with last message conversation set('lastmessage', messagetext); await conversation save(); console log('message sent successfully'); return message; } catch (error) { console error('error sending message ', error); throw error; } }; 设置消息的实时查询 以下是如何订阅实时消息更新: // subscribe to messages function const subscribetomessages = async (conversationid, onnewmessage) => { try { // create a query for messages in this conversation const message = parse object extend('message'); const query = new parse query(message); // get the conversation pointer const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; // set up query constraints query equalto('conversation', conversationpointer); query include('sender'); // subscribe to the query const subscription = await query subscribe(); // listen for create events (new messages) subscription on('create', (message) => { // process the message const newmessage = { id message id, text message get('text'), createdat message get('createdat'), sender { id message get('sender') id, username message get('sender') get('username'), avatar message get('sender') get('avatar') ? message get('sender') get('avatar') url() null }, read message get('read') }; // call the callback with the new message onnewmessage(newmessage); // mark the message as read if from another user if (message get('sender') id !== parse user current() id) { markmessageasread(message); } }); return subscription; } catch (error) { console error('error subscribing to messages ', error); throw error; } }; // helper to mark message as read const markmessageasread = async (message) => { try { message set('read', true); await message save(); } catch (error) { console error('error marking message as read ', error); } }; 实现输入指示器 以下是如何使用 livequery 实现输入指示器 // update typing status function const updatetypingstatus = async (conversationid, istyping) => { try { const currentuser = parse user current(); // get the conversation pointer const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; // check if typing status exists const typingstatus = parse object extend('typingstatus'); const query = new parse query(typingstatus); query equalto('conversation', conversationpointer); query equalto('user', currentuser); let typingstatus = await query first(); if (typingstatus) { // update existing status typingstatus set('istyping', istyping); } else { // create new status typingstatus = new typingstatus(); typingstatus set('conversation', conversationpointer); typingstatus set('user', currentuser); typingstatus set('istyping', istyping); } await typingstatus save(); } catch (error) { console error('error updating typing status ', error); } }; // subscribe to typing status function const subscribetotypingstatus = async (conversationid, ontypingstatuschange) => { try { // create a query for typing status in this conversation const typingstatus = parse object extend('typingstatus'); const query = new parse query(typingstatus); // get the conversation pointer const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; // set up query constraints query equalto('conversation', conversationpointer); query include('user'); // subscribe to the query const subscription = await query subscribe(); // listen for update events subscription on('update', (typingstatus) => { const user = typingstatus get('user'); const istyping = typingstatus get('istyping'); // call the callback with typing status ontypingstatuschange({ userid user id, username user get('username'), istyping }); }); // also listen for create events subscription on('create', (typingstatus) => { const user = typingstatus get('user'); const istyping = typingstatus get('istyping'); // call the callback with typing status ontypingstatuschange({ userid user id, username user get('username'), istyping }); }); return subscription; } catch (error) { console error('error subscribing to typing status ', error); throw error; } }; 第 8 步 — 实现搜索功能 让我们使用 back4app 的查询系统实现搜索功能。 搜索用户 以下是如何搜索用户 // search users function const searchusers = async (query, limit = 20) => { try { // create a query on the user class const userquery = new parse query(parse user); // search by username (case insensitive) userquery matches('username', new regexp(query, 'i')); // limit results to improve performance userquery limit(limit); // execute the query const users = await userquery find(); // process the results const userresults = users map(user => ({ id user id, username user get('username'), avatar user get('avatar') ? user get('avatar') url() null, bio user get('bio') || '' })); return userresults; } catch (error) { console error('error searching users ', error); throw error; } }; \### searching for posts here's how to search for posts by content ```javascript // search posts function const searchposts = async (query, limit = 20) => { try { // create a query on the post class const post = parse object extend('post'); const postquery = new parse query(post); // search for posts with content containing the query string postquery matches('content', new regexp(query, 'i')); // include the author information postquery include('author'); // sort by creation date (newest first) postquery descending('createdat'); // limit results postquery limit(limit); // execute the query const posts = await postquery find(); // process the results const postresults = posts map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); return postresults; } catch (error) { console error('error searching posts ', error); throw error; } }; 搜索标签 以下是如何搜索特定标签的帖子 // search hashtags function const searchhashtags = async (tag, limit = 20) => { try { // remove # if present at the beginning const hashtagquery = tag startswith('#') ? tag substring(1) tag; // create a query on the post class const post = parse object extend('post'); const postquery = new parse query(post); // search for posts with content containing the hashtag // using word boundaries to find actual hashtags postquery matches('content', new regexp(`#${hashtagquery}\\\b`, 'i')); // include the author information postquery include('author'); // sort by creation date (newest first) postquery descending('createdat'); // limit results postquery limit(limit); // execute the query const posts = await postquery find(); // process the results const hashtagresults = posts map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); return hashtagresults; } catch (error) { console error('error searching hashtags ', error); throw error; } }; 第9步 — 实现高级back4app功能 让我们探索一些可以增强您的社交网络应用程序的高级back4app功能。 云函数 back4app 允许您使用云函数实现服务器端逻辑。这些是运行在服务器上的 javascript 函数,可以从您的客户端应用程序中调用。 以下是一个用于跟踪评论通知的云函数示例: // in your back4app cloud code section parse cloud aftersave("comment", async (request) => { // only run for new comments, not updates if (request original) return; const comment = request object; const post = comment get("post"); const commenter = request user; // skip if user is commenting on their own post const postquery = new parse query("post"); const fullpost = await postquery get(post id, { usemasterkey true }); const postauthor = fullpost get("author"); if (postauthor id === commenter id) return; // create a notification const notification = parse object extend("notification"); const notification = new notification(); notification set("type", "comment"); notification set("fromuser", commenter); notification set("touser", postauthor); notification set("post", post); notification set("read", false); await notification save(null, { usemasterkey true }); }); 要实现这一点: 前往您的 back4app 控制面板 导航到 "云代码" > "云函数" 使用上述代码创建一个新函数 部署该函数 用于高级搜索的云代码 对于更复杂的搜索功能,您可以创建一个云函数: // in your back4app cloud code section parse cloud define("advancedsearch", async (request) => { const { query, type, limit = 20 } = request params; if (!query) { throw new error("search query is required"); } let results = \[]; switch (type) { case 'users' const userquery = new parse query(parse user); userquery matches('username', new regexp(query, 'i')); userquery limit(limit); results = await userquery find({ usemasterkey true }); break; case 'posts' const post = parse object extend('post'); const postquery = new parse query(post); postquery matches('content', new regexp(query, 'i')); postquery include('author'); postquery limit(limit); results = await postquery find({ usemasterkey true }); break; default throw new error("invalid search type"); } return results; }); 要从您的客户端调用此函数: // call the advanced search cloud function const calladvancedsearch = async (query, type, limit = 20) => { try { const results = await parse cloud run('advancedsearch', { query, type, limit }); return results; } catch (error) { console error('error calling advanced search ', error); throw error; } }; 实现后台作业 对于诸如趋势主题计算等重复任务,您可以使用后台作业: // in your back4app cloud code section parse cloud job("calculatetrendingtopics", async () => { // get posts from the last 7 days const post = parse object extend("post"); const query = new parse query(post); const oneweekago = new date(); oneweekago setdate(oneweekago getdate() 7); query greaterthan('createdat', oneweekago); // limit to a reasonable number for analysis query limit(1000); const posts = await query find({ usemasterkey true }); // extract hashtags and count occurrences const hashtagcounts = {}; posts foreach(post => { const content = post get('content') || ''; const hashtags = content match(/#(\w+)/g) || \[]; hashtags foreach(hashtag => { const tag = hashtag tolowercase(); hashtagcounts\[tag] = (hashtagcounts\[tag] || 0) + 1; }); }); // convert to array and sort by count const trendingarray = object entries(hashtagcounts) map((\[hashtag, count]) => ({ hashtag, count })) sort((a, b) => b count a count) slice(0, 10); // get top 10 // save to a trendingtopics class const trendingtopics = parse object extend("trendingtopics"); const trending = new trendingtopics(); trending set("topics", trendingarray); trending set("calculatedat", new date()); await trending save(null, { usemasterkey true }); return "trending topics calculated successfully"; }); 要设置此任务: 前往你的 back4app 控制面板 导航到 "云代码" > "后台任务" 使用上述代码创建一个新任务 安排它每天或每周运行 使用 parse 服务器钩子 back4app 允许您实现服务器端钩子,这些钩子会在某些操作之前或之后自动运行。这些钩子对于数据验证、修改或触发副作用非常有用。 // beforesave hook to sanitize post content parse cloud beforesave("post", async (request) => { const post = request object; let content = post get("content"); // sanitize content (example remove profanity) const profanitylist = \["badword1", "badword2", "badword3"]; profanitylist foreach(word => { const regex = new regexp(word, "gi"); content = content replace(regex, " "); }); // update the content post set("content", content); }); // afterdelete hook to clean up related data parse cloud afterdelete("post", async (request) => { const post = request object; const postid = post id; // delete all comments associated with this post const comment = parse object extend("comment"); const query = new parse query(comment); query equalto("post", post); const comments = await query find({ usemasterkey true }); await parse object destroyall(comments, { usemasterkey true }); console log(`deleted ${comments length} comments for post ${postid}`); }); 实现推送通知 back4app 提供了一个强大的推送通知系统,使您能够向用户的设备发送通知。以下是如何实现推送通知的步骤: 首先,在您的 back4app 控制面板中设置推送配置: 转到 "应用设置" > "推送" 配置您的 ios 和/或 android 凭据 当用户收到新消息时发送推送通知: // in your cloud code parse cloud aftersave("message", async (request) => { const message = request object; const conversation = message get("conversation"); const sender = message get("sender"); // get the conversation to find recipients const conversationquery = new parse query("conversation"); const fullconversation = await conversationquery get(conversation id, { usemasterkey true }); // get all participants const participants = fullconversation get("participants") || \[]; // send notification to all participants except the sender for (const participant of participants) { if (participant id !== sender id) { // create a query for this user's installations const pushquery = new parse query(parse installation); pushquery equalto("user", participant); // send the push notification await parse push send({ where pushquery, data { alert `new message from ${sender get("username")}`, sound "default", badge "increment", sender sender id, messageid message id, conversationid conversation id } }, { usemasterkey true }); } } }); 使用基于角色的访问控制 back4app 提供了一种基于角色的访问控制系统,允许您在更细粒度的级别上管理权限: // create an admin role const createadminrole = async (adminuser) => { try { // create a new role const adminrole = new parse role("administrator", new parse acl()); // set initial users of this role adminrole getusers() add(adminuser); // save the role await adminrole save(null, { usemasterkey true }); console log("admin role created successfully"); return adminrole; } catch (error) { console error("error creating admin role ", error); throw error; } }; // set permissions for posts to be moderated by admins const setpostmoderatorpermissions = async () => { try { // get the admin role const rolequery = new parse query(parse role); rolequery equalto("name", "administrator"); const adminrole = await rolequery first({ usemasterkey true }); // set class level permissions const schema = new parse schema("post"); // add class level permissions for admin role await schema setclp({ get { " " true }, find { " " true }, create { " " true }, update { "role\ administrator" true, "" true }, // creator and admins can update delete { "role\ administrator" true, "" true }, // creator and admins can delete addfield { "role\ administrator" true } }); await schema update(); console log("admin permissions for post class set successfully"); } catch (error) { console error("error setting admin permissions ", error); throw error; } }; 实现 webhooks back4app 允许您设置 webhooks 以与外部服务集成: // example cloud function that calls an external webhook parse cloud aftersave("post", async (request) => { const post = request object; // only trigger for new posts if (request original) return; try { // get author information const author = post get("author"); await author fetch({ usemasterkey true }); // prepare data for webhook const webhookdata = { postid post id, content post get("content"), authorname author get("username"), createdat post get("createdat") }; // call external webhook (example notify a content moderation service) const response = await parse cloud httprequest({ method 'post', url 'https //your webhook url com/new post', headers { 'content type' 'application/json' }, body webhookdata }); console log("webhook notification sent successfully ", response data); } catch (error) { console error("error sending webhook notification ", error); } }); 步骤 10 — 优化 back4app 性能 随着您的社交网络的增长,您需要优化 back4app 后端以提高性能和可扩展性。本节涵盖了确保您的应用程序在用户基础扩展时仍然快速和响应的基本策略。 数据库优化 高效的数据库设计和查询对应用程序性能至关重要。 创建索引 索引显著提高了对经常搜索字段的查询性能: 前往您的 back4app 控制面板 导航到 "数据库浏览器" > 选择一个类(例如,post) 点击 "索引" 标签 为经常搜索的字段创建索引: // example creating indexes programmatically const createpostindexes = async () => { try { const schema = new parse schema('post'); // add an index on the author field await schema addindex('author index', { author 1 }); // add an index on createdat for timeline queries await schema addindex('createdat index', { createdat 1 }); // add a compound index for author + createdat await schema addindex('author date index', { author 1, createdat 1 }); await schema update(); console log('indexes created successfully'); } catch (error) { console error('error creating indexes ', error); } }; 关键字段以进行索引: 作者 在 post 类中以加快用户特定查询 创建时间 以加快时间线加载 用户名 在用户类中以加快用户搜索 参与者 在对话类中以加快消息过滤 查询优化 优化您的查询以减少服务器负载和响应时间: // bad fetches all fields and doesn't use limit const fetchuserposts = async (userid) => { const query = new parse query('post'); query equalto('author', userid); const results = await query find(); return results; }; // good only fetches needed fields and uses limit const fetchuserposts = async (userid, page = 0, limit = 20) => { const post = parse object extend('post'); const query = new parse query(post); // get user pointer const userpointer = { type 'pointer', classname ' user', objectid userid }; // only get posts for this user query equalto('author', userpointer); // only select fields we need query select('content', 'image', 'likes', 'createdat'); // sort by date, newest first query descending('createdat'); // implement pagination query limit(limit); query skip(page limit); const results = await query find(); return results; }; 批量操作 对于涉及多个对象的操作,使用批量操作以减少api调用: // update multiple posts in a single request const updatemultipleposts = async (postids, updatedata) => { try { const post = parse object extend('post'); const posts = postids map(id => { const post = new post(); post id = id; return post; }); // set the same update data on all posts posts foreach(post => { object entries(updatedata) foreach((\[key, value]) => { post set(key, value); }); }); // save all posts in a single request await parse object saveall(posts); console log('all posts updated successfully'); } catch (error) { console error('error updating posts ', error); throw error; } }; // delete multiple comments in a single request const deletemultiplecomments = async (commentids) => { try { const comment = parse object extend('comment'); const comments = commentids map(id => { const comment = new comment(); comment id = id; return comment; }); // delete all comments in a single request await parse object destroyall(comments); console log('all comments deleted successfully'); } catch (error) { console error('error deleting comments ', error); throw error; } }; 实现缓存 为频繁访问的数据实现客户端缓存,以减少api调用: // caching user profiles const userprofilecache = new map(); const cache expiry = 10 60 1000; // 10 minutes in milliseconds const getuserprofile = async (userid, forcerefresh = false) => { // check if we have a valid cache entry const now = date now(); const cacheentry = userprofilecache get(userid); if (!forcerefresh && cacheentry && (now cacheentry timestamp < cache expiry)) { console log('using cached user profile'); return cacheentry data; } try { // fetch from back4app const query = new parse query(parse user); const user = await query get(userid); // process user data const userdata = { id user id, username user get('username'), bio user get('bio') || '', avatar user get('avatar') ? user get('avatar') url() null, followers user get('followers') || 0, following user get('following') || 0 }; // store in cache with timestamp userprofilecache set(userid, { data userdata, timestamp now }); return userdata; } catch (error) { console error('error fetching user profile ', error); // return cached data even if expired in case of error if (cacheentry) { console log('using expired cache due to error'); return cacheentry data; } throw error; } }; // clear cache for a user when their profile is updated const invalidateusercache = (userid) => { userprofilecache delete(userid); }; 高效使用livequery livequery对于实时功能非常强大,但应高效使用以避免性能问题: // efficient livequery usage const setupefficientlivequery = async (conversationid) => { try { // only subscribe to messages in the active conversation if (!conversationid) return null; // create a focused query const message = parse object extend('message'); const query = new parse query(message); // get the conversation pointer const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; // only get messages for this conversation query equalto('conversation', conversationpointer); // only include necessary fields query select('text', 'sender', 'createdat'); // limit initial results query limit(50); query descending('createdat'); // create subscription const subscription = await query subscribe(); subscription on('create', (message) => { // handle new message console log('new message ', message get('text')); // update ui with new message }); // unsubscribe when switching conversations return subscription; } catch (error) { console error('error setting up livequery ', error); return null; } }; // always unsubscribe when done const cleanuplivequery = (subscription) => { if (subscription) { subscription unsubscribe(); } }; livequery的最佳实践: 仅订阅需要实时更新的数据 限制您请求的字段,使用 select() 当数据不再需要时,始终取消订阅 使用约束来缩小订阅的范围 考虑将 livequery 与常规查询结合使用,以便初始数据加载 优化文件处理 高效的文件处理对于具有图像上传的社交网络至关重要: // resize images before upload to reduce storage and bandwidth const uploadresizedimage = async (originalfile, maxwidth = 1200, maxheight = 1200) => { return new promise((resolve, reject) => { try { const reader = new filereader(); reader onload = (event) => { const img = new image(); img onload = () => { // calculate new dimensions let width = img width; let height = img height; if (width > maxwidth) { height = math round(height (maxwidth / width)); width = maxwidth; } if (height > maxheight) { width = math round(width (maxheight / height)); height = maxheight; } // create canvas for resizing const canvas = document createelement('canvas'); canvas width = width; canvas height = height; // draw resized image const ctx = canvas getcontext('2d'); ctx drawimage(img, 0, 0, width, height); // convert to blob canvas toblob(async (blob) => { const resizedfile = new file(\[blob], originalfile name, { type originalfile type, lastmodified date now() }); // upload to back4app const parsefile = new parse file(resizedfile name, resizedfile); await parsefile save(); resolve(parsefile); }, originalfile type, 0 8); // 0 8 quality for jpeg }; img src = event target result; }; reader readasdataurl(originalfile); } catch (error) { reject(error); } }); }; // use thumbnails for preview images const createthumbnailversion = async (originalfile, thumbnailwidth = 200) => { // similar to above but creating a smaller thumbnail // // return both original and thumbnail return { original originalparsefile, thumbnail thumbnailparsefile }; }; 监控您的 back4app 应用 back4app 提供了几种工具来监控您的应用性能: 仪表板分析 提供高层次的使用统计 监控 api 请求、存储使用和文件操作 识别使用模式和高峰时段 日志 提供操作的详细信息 查看服务器日志以查找错误和性能问题 按类型过滤日志以隔离问题 性能指标 跟踪响应时间和系统健康 监控查询性能 识别慢操作 配置警报以通知潜在问题: 在您的 back4app 控制面板中转到 "应用设置" > "警报" 设置以下警报: 高 api 使用量(接近您的计划限制) 错误率激增(表明应用程序问题) 服务器停机(影响所有用户) 存储使用量(接近容量) // implementing custom logging for performance monitoring const logperformance = async (operation, starttime) => { const duration = date now() starttime; // log to back4app const performancelog = parse object extend('performancelog'); const log = new performancelog(); log set('operation', operation); log set('duration', duration); log set('user', parse user current() ? parse user current() id 'anonymous'); log set('timestamp', new date()); await log save(null, { usemasterkey true }); // log locally too console log(`performance ${operation} took ${duration}ms`); }; // example usage const fetchtimelineposts = async () => { const starttime = date now(); try { // query code here // // calculate and log performance await logperformance('fetchtimelineposts', starttime); return results; } catch (error) { console error('error ', error); throw error; } }; 扩展策略 随着您的应用程序增长,请考虑以下扩展策略: 升级您的 back4app 计划 随着用户基础的扩大,升级到更高的级别 监控使用情况,并在达到限制之前进行升级 为大型集合实施分片 将数据分散到多个类中,以处理非常大的数据集 例如,按年或月拆分消息 // example of sharded messages implementation const sendshardedmessage = async (conversationid, text, sender) => { try { // determine which shard to use (e g , by current month) const now = new date(); const shardname = `message ${now\ getfullyear()} ${now\ getmonth() + 1}`; // create dynamic class reference const messageshard = parse object extend(shardname); const message = new messageshard(); // create a pointer to the conversation const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; // set message data message set('conversation', conversationpointer); message set('text', text); message set('sender', sender); // save to appropriate shard await message save(); return message; } catch (error) { console error('error sending sharded message ', error); throw error; } }; // fetch messages across shards const fetchshardedmessages = async (conversationid, limit = 50) => { try { // get list of all message shards const shards = await getmessageshardnames(); // create a query for each shard const queries = shards map(shardname => { const messageshard = parse object extend(shardname); const query = new parse query(messageshard); // create a pointer to the conversation const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; query equalto('conversation', conversationpointer); return query; }); // combine queries const mainquery = parse query or( queries); mainquery descending('createdat'); mainquery limit(limit); return await mainquery find(); } catch (error) { console error('error fetching sharded messages ', error); throw error; } }; 实现缓存服务器 使用 redis 或其他缓存解决方案来处理频繁访问的数据 通过云代码和外部服务实现 使用内容分发网络(cdn) 配置 cdn 以加快文件传输 对图像和媒体文件尤其重要 安全优化 安全性和性能密切相关。这些优化同时改善两者 实现指针权限 使用指针权限限制对相关对象的访问 这允许更高效的查询而不牺牲安全性 // set up pointer permissions const setuppointerpermissions = async () => { try { // get the message class schema const schema = new parse schema('message'); // add pointer permissions only conversation participants can access messages const pointerpermissions = { 'conversation' { 'read' { 'participantsfield' 'participants' } } }; // update the schema with pointer permissions await schema addpointerpermissions(pointerpermissions); await schema update(); console log('pointer permissions set successfully'); } catch (error) { console error('error setting pointer permissions ', error); } }; 有效使用parse acls acl提供安全性,但如果过度使用可能会影响性能 考虑在常见情况下使用类级别权限,而在例外情况下使用acl // efficient acl usage set default acl parse setdefaultacl(new parse acl({ publicreadaccess true }), true); // only use specific acls for private content const createprivatepost = async (content, image) => { const currentuser = parse user current(); // create a new private post const post = parse object extend('post'); const post = new post(); // set post data post set('content', content); post set('author', currentuser); if (image) { const parsefile = new parse file(image name, image); await parsefile save(); post set('image', parsefile); } // create private acl const privateacl = new parse acl(currentuser); // only the creator can read and write privateacl setreadaccess(currentuser id, true); privateacl setwriteaccess(currentuser id, true); // set the acl post setacl(privateacl); // save the post await post save(); return post; }; back4app 服务器优化 优化您在 back4app 中的 parse 服务器配置: 配置服务器参数 转到 "应用设置" > "服务器设置" 调整长时间运行操作的超时设置 配置最大请求限制 设置速率限制 实施速率限制以防止滥用并确保公平使用 如前所示通过云代码进行配置 通过实施这些性能优化策略,您的社交网络应用将能够很好地应对增长,并保持出色的性能,即使在用户基础扩展的情况下。请记住持续监控应用的性能,并根据需要进行逐步改进。 结论 在本教程中,您已经学习了如何使用 back4app 作为后端服务构建完整的社交网络应用程序。您实现了核心功能,如: 用户身份验证和个人资料管理 帖子创建和社交互动 使用 livequery 的实时消息传递 搜索功能 高级功能,如云函数和后台作业 back4app 的 parse server 提供了强大且可扩展的后端解决方案,使您能够专注于构建出色的用户体验,而无需担心复杂的服务器基础设施。 下一步 为了进一步增强您的社交网络应用程序,请考虑: 实现推送通知 使用 back4app 的推送服务 添加分析功能 以跟踪用户参与度 设置文件存储选项 以处理更大的媒体文件 实现内容审核 使用云函数 创建移动应用程序 使用 react native 和相同的 back4app 后端 通过利用 back4app 的功能并遵循本教程中概述的优化策略,您可以构建一个能够处理数千用户的社交网络,同时保持高性能。 其他资源 为了继续扩展您对 back4app 的知识,这里有一些有价值的资源: back4app 文档 back4app 文档 https //www back4app com/docs/get started/welcome parse javascript指南 https //docs parseplatform org/js/guide/ back4app youtube频道 https //www youtube com/c/back4app 解析服务器和实时查询 parse server 文档 https //docs parseplatform org/parse server/guide/ 实时查询指南 https //docs parseplatform org/parse server/guide/#live queries 安全与优化 类级权限指南 https //docs parseplatform org/rest/guide/#class level permissions acl安全指南 https //docs parseplatform org/js/guide/#security 性能优化 https //docs parseplatform org/js/guide/#performance 高级功能 云代码指南 https //docs parseplatform org/cloudcode/guide/ 后台作业 https //docs parseplatform org/cloudcode/guide/#cloud jobs 推送通知 https //docs parseplatform org/js/guide/#push notifications 请记住,建立一个成功的社交网络是一个迭代的过程。首先要有一个坚实的基础(你现在已经有了),收集用户反馈,并根据实际使用模式不断改进你的应用程序。 我们希望这个教程能为你提供知识和信心,以便使用back4app构建出令人惊叹的应用程序。祝编码愉快!