在这一章中,你将使用 MERN 框架建立一个基于照片的社交网络。后端托管在 Heroku,前端站点使用 Firebase 托管。Firebase 也处理身份验证功能。
Material-UI 提供了项目中的图标。使用 Pusher 是因为 MongoDB 不像 Firebase 那样是实时数据库。您希望帖子反映出某人点击提交按钮的瞬间。
有了这个基于照片的功能性社交网络,你可以从你的电脑上传图片并写下描述。用户通过电子邮件登录。最终托管的 app 如图 5-1 所示。
图 5-1
最终应用
首先,在你的终端上创建一个photo-social-mern文件夹。在里面,它使用创建-反应-应用来创建一个名为照片-社交-前端的新应用。以下是命令。
mkdir photo-social-mern
cd photo-social-mern
npx create-react-app photo-social-frontend由于前端站点是通过 Firebase 托管的,所以可以在 create-react-app 创建 React app 的同时创建基本设置。按照第 1 章的设置说明,我在 Firebase 控制台中创建了图片社交网。
由于使用了认证功能,您需要进行第 4 章中提到的额外配置,并使用您需要复制的firebaseConfig(参见图 5-2 )。
图 5-2
配置
在 Visual Studio Code (VSCode)中打开代码,在src文件夹中创建一个firebase.js文件,并将配置内容粘贴到那里。
const firebaseConfig = {
apiKey: "AIxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxY",
authDomain: "photo-xxxxxxxxxxxxxxxxxxxxxxx.com",
projectId: "photo-xxxxxxxxxxx",
storageBucket: "photo-xxxxxxxxxxxx",
messagingSenderId: "52xxxxxxx",
appId: "1:52xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
};返回 React 项目,将cd返回到photo-social-frontend目录。用npm start启动 React 应用。
cd photo-social-frontend
npm start在index.js、App.js和App.css中删除文件和基本设置就像在第 2 章中所做的一样。遵循这些指示。图 5-3 显示了应用在 localhost 上的外观。
图 5-3
初始应用
让我们创建应用标题,这是一个很好的标志。在App.js文件中,用app__header类名创建一个 div,并使用 public 文件夹中的 React 徽标,这是每个 React 项目都附带的。更新的内容用粗体标记。
import './App.css';
function App() {
return (
<div className="app">
<div className="app__header">
<img className="app__headerImage" src="logo192.png" alt="Header" />
</div>
</div>
);
}
export default App;接下来,开始在App.css文件中编写样式。在这里,您为应用、app__header和app__headerImage类编写样式。
.app {
background-color: #fafafa;
}
.app__header{
background-color: white;
padding: 20px;
border-bottom: 1px solid lightgray;
object-fit: contain;
}
.app__headerImage {
object-fit: contain;
margin-left: 10px;
height: 40px;
}图 5-4 显示了 localhost 上的 logo。
图 5-4
完美的标志
现在让我们创建 post 组件,它包含登录用户的头像,包括一张照片和一个简短的描述。在src文件夹中创建一个components文件夹。然后,在components文件夹中创建两个文件——Post.js和Post.css。
Post.js文件是一个简单的功能组件,包含用户名、图片和帖子。
import React from 'react'
import './Post.css'
const Post = () => {
return (
<div className="post">
<h3>TWD</h3>
<img className="post__image" src="https://www.techlifediary.com/wp-content/uploads/2020/06/react-js.png" alt="React" />
<h4 className="post__text"><strong>thewebdev</strong>🔥Build a Messaging app with MERN (MongoDB, Express, React JS, Node JS) 🔥</h4>
</div>
)
}
export default Post在App.js文件中,包含三次Post组件。更新的内容用粗体标记。
import './App.css';
import Post from './components/Post';
function App() {
return (
<div className="app">
<div className="app__header">
<img className="app__headerImage" src="logo192.png" alt="Header" />
</div>
<Post />
<Post />
<Post />
</div>
);
}
export default App;图标来自于 Material-UI ( https://material-ui.com )。首先,根据文档进行两次 npm 安装。通过photo-social-frontend文件夹中的集成端子安装铁芯。
npm i @material-ui/core @material-ui/icons在Post.js中,从 Material-UI 添加一个头像图标。在一个post__header div 中,它和h3标签一起使用。更新的内容用粗体标记。
...
import { Avatar } from '@material-ui/core'
const Post = () => {
return (
<div className="post">
<div className="post__header">
<Avatar
className="post__avatar"
alt="TWD"
src="/statimg/avatar/1.jpg"
/>
<h3>TWD</h3>
</div>
<img className="post__image" src="https://www.techlifediary.com/wp-content/uploads/2020/06/react-js.png" alt="React" />
...
</div>
)
}
export default Post接下来,在Post.css文件中添加样式。
.post {
background-color: white;
max-width: 800px;
border: 1px solid lightgray;
margin-bottom: 45px;
}
.post__image {
width: 100%;
object-fit: contain;
border-top: 1px solid lightgray;
border-bottom: 1px solid lightgray;
}
.post__text {
font-weight: normal;
padding: 20px;
}
.post__header {
display: flex;
align-items: center;
padding: 20px;
}
.post__avatar {
margin-right: 10px;
}图 5-5 显示了应用现在在 localhost 上的样子。
图 5-5
风格帖子
让我们把一切都动态化,把用户名、标题和图片 URL 作为道具传递。在Post.js中,进行以下更改。更新的内容用粗体标记。
...
import { Avatar } from '@material-ui/core'
const Post = ({ username, caption, imageUrl }) => {
return (
<div className="post">
<div className="post__header">
<Avatar
className="post__avatar"
alt= {username}
src="/statimg/avatar/1.jpg"
/>
<h3> {username}</h3>
</div>
<img className="post__image" src={imageUrl} alt="React" />
<h4 className="post__text"><strong>{username}</strong>{caption}</h4>
</div>
)
}
export default Post接下来,我们来优化一下App.js中的代码。这里,您使用useState钩子来创建新的状态帖子。这里的柱子是数组中的对象。
在 return 语句中,映射 posts 数组并显示每个帖子。更新的内容用粗体标记。
...
import React, { useEffect, useState } from 'react';
function App() {
const [posts, setPosts] = useState([
{
username: "TWD",
caption: "🔥Build a Messaging app with MERN Stack🔥",
imageUrl: "https://www.techlifediary.com/wp-content/uploads/2020/06/react-js.png"
},
{
username: "nabendu82",
caption: "Such a beautiful world",
imageUrl: "https://quotefancy.com/media/wallpaper/3840x2160/126631-Charles-Dickens-Quote-And-a-beautiful-world-you-live-in-when-it-is.jpg"
}
])
return (
<div className="app">
<div className="app__header">
<img className="app__headerImage" src="logo192.png" alt="Header" />
</div>
{posts.map(post => (
<Post username={post.username} caption={post.caption} imageUrl={post.imageUrl} />
))}
</div>
);
}
export default App;图 5-6 显示在 localhost 上。
图 5-6
一切动态
让我们来看看 Firebase 身份验证,它允许您登录应用并发布内容。这个项目使用基于电子邮件的认证,这不同于前一章中的 Google 认证。
你需要回到火焰基地。点击认证选项卡,然后点击开始按钮,如图 5-7 所示。
图 5-7
开始
在下一个界面中,点击邮箱/密码的编辑图标,如图 5-8 所示。
图 5-8
电子邮件和密码
在弹出的窗口中,点击启用按钮,然后点击保存按钮,如图 5-9 所示。
图 5-9
启用电子邮件和密码
现在,让我们展示一个来自 Material-UI 的注册模型。这个代码来自 https://material-ui.com/components/modal/#modal 。
首先,在App.js文件中导入几个依赖项和两个样式。在那之后,你就有了类和模态样式的常量。打开状态最初设置为假。
返回内部,将模态和注册按钮的打开状态设置为真。
更新的内容用粗体标记。
...
import { makeStyles } from '@material-ui/core/styles';
import Modal from '@material-ui/core/Modal';
import { Button, Input } from '@material-ui/core';
function getModalStyle() {
const top = 50;
const left = 50;
return {
top: `${top}%`,
left: `${left}%`,
transform: `translate(-${top}%, -${left}%)`,
};
}
const useStyles = makeStyles((theme) => ({
paper: {
position: 'absolute',
width: 400,
backgroundColor: theme.palette.background.paper,
border: '2px solid #000',
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3),
},
}));
function App() {
const classes = useStyles();
const [modalStyle] = React.useState(getModalStyle);
const [open, setOpen] = useState(false)
...
return (
<div className="app">
<Modal open={open} onClose={() => setOpen(false)}>
<div style={modalStyle} className={classes.paper}>
<h2>Modal Code</h2>
</div>
</Modal>
<div className="app__header">...</div>
<Button onClick={() => setOpen(true)}>Sign Up</Button>
{posts.map(post => (
<Post ={post.username} caption={post.caption} imageUrl={post.imageUrl} />
))}
</div>
);
}
export default App;在 localhost 上,点击注册按钮,获得带文本的模态(见图 5-10 )。
图 5-10
模式弹出菜单
在创建表单之前,您需要在App.js文件中创建三个状态变量——username、email,和password。
用户名、电子邮件和密码的字段在App.js文件的模式中。还有一个按钮包含一个调用signUp函数的onClick处理程序。
更新的内容用粗体标记。
...
function App() {
...
const [username, setUsername] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
...
const signUp = e => {
e.preventDefault()
}
return (
<div className="app">
<Modal open={open} onClose={() => setOpen(false)}>
<div style={modalStyle} className={classes.paper}>
<form className="app__signup">
<center>
<img className="app__headerImage" src="logo192.png" alt="Header" />
</center>
<Input placeholder="username"
type="text"
value={username}
onChange={e => setUsername(e.target.value)}
/>
<Input placeholder="email"
type="text"
value={email}
onChange={e => setEmail(e.target.value)}
/>
<Input placeholder="password"
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
/>
<Button type="submit" onClick={signUp}>Sign Up</Button>
</form>
</div>
</Modal>
<div className="app__header">...</div>
...
</div>
);
}
export default App;在App.css文件中,为app__signup类添加样式。
.app__signup {
display: flex;
flex-direction: column;
}图 5-11 显示点击 localhost 上的注册按钮打开一个表单。
图 5-11
注册表单
让我们从用于身份验证的 Firebase 设置开始。首先,在photo-social-frontend文件夹中安装 Firebase 的所有依赖项。
npm i firebase接下来,更新firebase.js文件以使用配置来初始化应用。更新的内容用粗体标记。
import firebase from 'firebase';
const firebaseConfig = {
...
};
const firebaseApp = firebase.initializeApp(firebaseConfig)
const db = firebaseApp.firestore()
const auth = firebase.auth()
const storage = firebase.storage()
export { db, auth, storage }让我们为应用添加身份验证。首先,从本地 Firebase 导入 auth,然后在App.js文件中添加一个新的user状态变量。
向使用 Firebase 中的createUserWithEmailAndPassword并传递电子邮件和密码的signUp函数添加代码。之后,更新用户,将displayName设置为用户名。使用useEffect钩子来监控任何用户更改,并使用setUser()来更新user变量。
在返回界面中,检查用户是否登录,然后显示注销按钮或注册按钮。
更新的内容用粗体标记。
import { auth } from './firebase'
...
function App() {
...
const [user, setUser] = useState(null)
...
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(authUser => {
if(authUser) {
console.log(authUser)
setUser(authUser)
} else {
setUser(null)
}
})
return () => {
unsubscribe()
}
}, [user, username])
const signUp = (e) => {
e.preventDefault()
auth.createUserWithEmailAndPassword(email, password)
.then(authUser => authUser.user.updateProfile({ displayName: username }))
.catch(error => alert(error.message))
setOpen(false)
}
return (
<div className="app">
<Modal open={open} onClose={() => setOpen(false)}>...</Modal>
<div className="app__header">...</div>
{user ? <Button onClick={() => auth.signOut()}>Logout</Button> : <Button onClick={() => setOpen(true)}>Sign Up</Button>}
...
</div>
);
}
export default App;身份验证在本地主机上正常工作。你可以注册一个新用户,如图 5-12 所示。
图 5-12
用户注册
现在让我们通过在App.js文件中创建一个新的登录按钮和一个新的模态组件来处理登录功能。
首先,在App.js文件中创建openSignIn状态变量和函数。该函数包含来自 Firebase 的signInWithEmailAndPassword。
注意,只使用了 email 和密码,但是有一个新的openSignIn状态变量和它的setOpenSignIn setter。更新的内容用粗体标记。
...
function App() {
...
const [openSignIn, setOpenSignIn] = useState(false)
...
const signIn = e => {
e.preventDefault()
auth.signInWithEmailAndPassword(email, password)
.catch(error => alert(error.message))
setOpenSignIn(false)
}
return (
<div className="app">
<Modal open={open} onClose={() => setOpen(false)}>...</Modal>
<Modal open={openSignIn} onClose={() => setOpenSignIn(false)}>
<div style={modalStyle} className={classes.paper}>
<form className="app__signup">
<center>
<img className="app__headerImage" src="logo192.png" alt="Header" />
</center>
<Input placeholder="email" type="text" value={email}
onChange={e => setEmail(e.target.value)} />
<Input placeholder="password" type="password" value={password}
onChange={e => setPassword(e.target.value)} />
<Button type="submit" onClick={signIn}>Sign In</Button>
</form>
</div>
</Modal>
<div className="app__header">...</div>
{user ? <Button onClick={() => auth.signOut()}>Logout</Button> : (
<div className="app__loginContainer">
<Button onClick={() => setOpenSignIn(true)}>Sign In</Button>
<Button onClick={() => setOpen(true)}>Sign Up</Button>
</div>
)}}
...
</div>
);
}
export default App;localhost 上的按钮有了新的标志。它会打开一个弹出窗口来输入凭证(参见图 5-13 )。使用您为登录按钮输入的相同凭据,您可以成功登录。
图 5-13
登录弹出窗口
Firebase 用户身份验证已完成。添加帖子的代码并上传图片。一旦你开始后端,你就回到这个部分。
在components文件夹中新建文件ImageUpload.js和ImageUpload.css,并导入到App.js文件中。接下来,在App.js文件中传递来自ImageUpload的道具用户名。
在App.js,中,创建一个具有app__posts类名的新 div,并在其中包含文章。App.js文件的更新内容用粗体标记。
...
import ImageUpload from './components/ImageUpload';
...
function App() {
...
return (
<div className="app">
...
{user ? <Button onClick={() => auth.signOut()}>Logout</Button> :(
...
)}
<div className="app__posts">
{posts.map(post => (
<Post username={post.username} caption={post.caption} imageUrl={post.imageUrl} />
))}
</div>
{user?.displayName ? <ImageUpload username={user.displayName} /> : <h3 className="app__notLogin">Need to login to upload</h3>}
</div>
);
}
export default App;在ImageUpload.js文件中,从基本内容开始。有一个标题输入框和另一个图像输入框。还有一个按钮和一个进度条。
以下是ImageUpload.js文件的内容。
import React, { useState } from 'react'
import './ImageUpload.css'
const ImageUpload = ({ username }) => {
const [image, setImage] = useState(null)
const [progress, setProgress] = useState(0)
const [caption, setCaption] = useState('')
const handleChange = e => {
if(e.target.files[0]) {
setImage(e.target.files[0])
}
}
const handleUpload = () => {}
return (
<div className="imageUpload">
<progress className="imageUpload__progress" value={progress} max="100" />
<input
type="text"
placeholder="Enter a caption..."
className="imageUpload__input"
value={caption}
onChange={e => setCaption(e.target.value)}
/>
<input className="imageUpload__file" type="file" onChange={handleChange} />
<button className="imageUpload__button" onClick={handleUpload}>Upload</button>
</div>
)
}
export default ImageUpload前端几乎完成,但你需要完成造型。首先,在ImageUpload.css文件中添加样式。以下是该文件的内容。
.imageUpload {
display: flex;
flex-direction: column;
max-width: 800px;
width: 100%;
margin: 10px auto;
}
.imageUpload__progress{
width: 100%;
margin-bottom: 10px;
}
.imageUpload__input{
padding: 10px;
margin-bottom: 10px;
}
.imageUpload__file {
margin-bottom: 10px;
}
.imageUpload__button {
border: none;
color: lightgray;
background-color: #6082a3;
cursor: pointer;
padding: 10px;
font-weight: bolder;
font-size: 0.9rem;
}
.imageUpload__button:hover {
color: #6082a3;
background-color: lightgray;
}图 5-14 显示了本地主机上的图像上传。
图 5-14
图像上传
在App.css文件中添加样式。更新后的代码用粗体标记。它保留了app__signup和app__headerImage的现有代码。
.app {
display:grid;
place-items: center;
background-color: #fafafa;
}
.app__header{
display: flex;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 1;
width: 100%;
background-color: white;
padding: 20px;
border-bottom: 1px solid lightgray;
object-fit: contain;
}
.app__notLogin{
margin-bottom: 20px;
}
.app__loginContainer{
margin-right: 10px;
}
.app__posts {
padding: 20px;
}在App.js中有一个小的修正,将用户代码移动到app__header div 中。更新后的代码用粗体标记。
...
function App() {
...
return (
<div className="app">
...
<div className="app__header">
<img className="app__headerImage" src="logo192.png" alt="Header" />
{user ? <Button onClick={() => auth.signOut()}>Logout</Button> :(
<div className="app__loginContainer">
<Button onClick={() => setOpenSignIn(true)}>Sign In</Button>
<Button onClick={() => setOpen(true)}>Sign Up</Button>
</div>
)}
</div>
...
</div>
);
}
export default App;图 5-15 显示了本地主机上桌面视图中的应用。
图 5-15
前端完成
让我们转到后端,从 Node.js 代码开始。打开一个新的终端窗口,在根目录下创建一个新的photo-social-backend文件夹。移动到photo-social-backend目录后,输入git init命令,这是 Heroku 稍后需要的。
mkdir photo-social-backend
cd photo-social-backend
git init接下来,通过在终端中输入npm init命令来创建package.json文件。你被问了一堆问题;对于大多数情况,只需按下回车键。你可以提供描述和作者,但不是强制的。通常在标准的server.js,处设置进入点(见图 5-16 )。
图 5-16
初始后端
一旦package.json被创建,您需要创建包含node_modules的.gitignore文件,因为您不想以后将 node_modules 推送到 Heroku。以下是.gitignore文件内容。
node_modules接下来,打开package.json.需要在 Node.js 中启用类似 React 的导入,包括一个启动脚本来运行server.js文件。更新的内容用粗体标记。
{
"name": "messaging-app-backend",
"version": "1.0.0",
"description": "Messaging app backend",
"main": "server.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "Nabendu Biswas",
"license": "ISC"
}最后,您需要在启动之前安装两个软件包。打开终端,在photo-social-backend文件夹中安装 Express 和 Mongoose。
npm i express mongooseMongoDB 的设置与第 1 章中描述的相同。按照这些说明,创建一个名为 photo-social-mern 的新项目。
在继续之前,将nodemon安装在photo-social-backend文件夹中。它帮助 server.js 中的更改即时重启 Node 服务器。
npm i nodemon在photo-social-backend文件夹中创建一个server.js文件。在这里,您导入 Express 和 Mongoose 包。然后使用 Express 创建一个运行在端口 9000 上的port变量。
第一个 API 端点是一个由app.get()创建的简单 GET 请求,如果成功,它会显示文本 Hello TheWebDev 。
然后用app.listen()监听端口。
import express from 'express'
import mongoose from 'mongoose'
//App Config
const app = express()
const port = process.env.PORT || 9000
//Middleware
//DB Config
//API Endpoints
app.get("/", (req, res) => res.status(200).send("Hello TheWebDev"))
//Listener
app.listen(port, () => console.log(`Listening on localhost: ${port}`))在终端输入 nodemon server.js 查看监听 localhost: 9000 控制台日志。为了检查路线是否正常工作,转到http://localhost:9000/查看终点文本,如图 5-17 所示。
图 5-17
初始路线
在 MongoDB 中,您需要创建一个数据库用户并授予网络访问权限。该过程与第 1 章中的解释相同。遵循这些说明,然后获取用户凭证和连接 URL。
在server.js文件中,创建一个connection_url变量,并将 URL 粘贴到 MongoDB 的字符串中。您需要提供之前保存的密码和数据库名称。
更新后的代码用粗体标记。
...
//App Config
const app = express()
const port = process.env.PORT || 9000
const connection_url = ' mongodb+srv://admin:<password>@cluster0.giruc.mongodb.net/photoDB?retryWrites=true&w=majority'
//Middleware
//DB Config
mongoose.connect(connection_url, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true
})
//API Endpoints
app.get("/", (req, res) => res.status(200).send("Hello TheWebDev"))
...让我们为帖子创建一个模型。在photo-social-backend文件夹中创建一个postModel.js文件。
首先,用需要传递的参数创建一个模式,然后导出它。
import mongoose from 'mongoose'
const postsModel = mongoose.Schema({
caption: String,
user: String,
image: String
})
export default mongoose.model('posts', postsModel)现在,您可以使用该模式来创建向数据库添加数据的端点。
在server.js中,创建一个到/upload端点的 POST 请求。负载在req.body到 MongoDB。然后使用create()发送dbPost.如果成功,您将收到状态 201;否则,您会收到状态 500。
接下来,创建/sync的 GET 端点,从数据库中获取数据。你在这里用的是find()。如果成功,您将收到状态 200(否则,状态 500)。
更新后的代码用粗体标记。
import express from 'express'
import mongoose from 'mongoose'
import Posts from './postModel.js'
...
//API Endpoints
app.get("/", (req, res) => res.status(200).send("Hello TheWebDev"))
app.post('/upload', (req, res) => {
const dbPost = req.body
Posts.create(dbPost, (err, data) => {
if(err)
res.status(500).send(err)
else
res.status(201).send(data)
})
})
app.get('/sync', (req, res) => {
Posts.find((err, data) => {
if(err) {
res.status(500).send(err)
} else {
res.status(200).send(data)
}
})
})
//Listener
app.listen(port, () => console.log(`Listening on localhost: ${port}`))在处理 POST 请求之前,您需要完成两件事情。第一,实行 First 否则,当您稍后部署应用时,会出现跨来源错误。打开终端,在photo-social-backend文件夹中安装 CORS。
npm i cors在server.js中,导入 CORS,然后配合app.use()使用。你还需要使用express.json()中间件。更新后的代码用粗体标记。
import express from 'express'
import mongoose from 'mongoose'
import Cors from 'cors'
import Posts from './postModel.js'
...
//Middleware
app.use(express.json())
app.use(Cors())
...在 Postman 中,您需要将请求更改为 POST,然后添加http://localhost:9000/upload端点。
之后,点击正文然后选择 raw 。从下拉菜单切换到 JSON(应用/json) 。在文本编辑器中,输入如图 5-18 所示的数据。要改变的一件事是通过给键加上双引号来使数据 JSON。
图 5-18
邮递员邮件
接下来,点击发送按钮。如果一切正确,你得到状态:201 已创建,如图 5-18 所示。
我以类似的方式插入了其他数据。您需要测试 GET /sync端点。将请求更改为 GET,然后单击发送按钮。如果一切正确,你得到状态:200 OK ,如图 5-19 所示。
图 5-19
邮递员得到
有时在发布请求时,服务器会出错。错误为UnhandledPromiseRejectionWarning:MongooseServerSelectionError:connection。
如果您遇到此错误,请转到您的网络访问选项卡,并点击添加 IP 地址按钮。之后点击添加当前 IP 地址按钮,点击确认,如图 5-20 所示。
图 5-20
添加当前 IP
你想在应用初始加载时获取所有消息,然后推送消息。您需要达到 GET 端点,为此您需要 Axios。打开photo-social-frontend文件夹并安装。
npm i axios接下来,在src文件夹中创建一个新的axios.js文件,然后创建一个axios的实例。基础 URL 是http://localhost:9000。
import axios from 'axios'
const instance = axios.create({
baseURL: "http://localhost:9000"
})
export default instance在ImageUpload.js文件中,从 Firebase 和 Axios 导入存储。更新handleUpload(),点击上传按钮后触发。
首先,在uploadTask变量中取上传的图片路径,放入数据库。检查state_changed因为快照改变了。根据上传的 has 数量,更新setProgress中的进度条。
之后,你需要做错误管理。从 Firebase 获取图像 URL。
接下来,获取标题、用户名和 URL,并在 MongoDB 中执行axios.post到/upload的操作。
更新后的代码用粗体标记。
...
import { storage } from "../firebase";
import axios from '../axios'
const ImageUpload = ({ username }) => {
...
const [url, setUrl] = useState("");
const handleChange = e => {...}
const handleUpload = () => {
const uploadTask = storage.ref(`img/${image.name}`).put(image);
uploadTask.on(
"state_changed",
(snapshot) => {
const progress = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
setProgress(progress);
},
(error) => {
console.log(error);
},
() => {
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then((url) => {
setUrl(url);
axios.post('/upload', {
caption: caption,
user: username,
image: url
})
setProgress(0);
setCaption("");
setImage(null);
});
}
);
};
return (...)
}
export default ImageUpload在测试之前,您需要在 Firebase 控制台中设置存储。首先点击存储选项卡,然后点击开始按钮,弹出如图 5-21 所示的窗口。然后,点击下一个按钮。
图 5-21
燃料库
在下一个画面中,点击完成按钮,如图 5-22 所示。
图 5-22
云存储
进入本地主机,上传任何图片,输入标题,点击上传按钮。你可以看到帖子被保存到 MongoDB(见图 5-23 )。
图 5-23
蒙戈布省省市镇
在App.js中,你需要从 MongoDB 中获取帖子。首先,导入本地axios。然后创建一个新的useEffect钩子,并向/sync端点发出 GET 请求。
接下来,用从 MongoDB 收到的数据更新App.js。
更新后的代码用粗体标记。
...
import axios from './axios'
...
function App() {
...
const fetchPosts = async () => {
await axios.get("/sync").then(response => setPosts(response.data))
}
useEffect(() => {
fetchPosts()
},[])
...
return (
<div className="app">
...
<div className="app__posts">
{posts.map(post => (
<Post
key={post._id}
username={post.user}
caption={post.caption}
imageUrl={post.image}
/>
))}
</div>
...
</div>
);
}
export default App;图 5-24 显示了来自本地主机上的 MongoDB 数据库的 post。
图 5-24
来自 MongoDB 的帖子
既然 MongoDB 不是实时数据库,那就该给 app 加一个 pusher 来获取实时数据了。因为你已经完成了第四章的设置,按照同样的说明,创建一个名为的应用。
同样,您需要停止服务器并安装 Pusher。在photo-social-backend文件夹中,用下面的命令安装它。
npm i pusher在server.js文件中,导入它,然后使用推动器初始化代码。从 Pusher 网站获取初始化代码( https://pusher.com )。要添加代码,用db.once打开一个数据库连接。然后用watch()观看来自 MongoDB 的消息集合。
在changeStream里面,如果operationType被插入,你把数据插入到推动器里。更新后的代码用粗体标记。
...
import Pusher from 'pusher'
...
//App Config
...
const pusher = new Pusher({
appId: "11xxxx",
key: "9exxxxxxxxxxxxx",
secret: "b7xxxxxxxxxxxxxxx",
cluster: "ap2",
useTLS: true
});
//API Endpoints
mongoose.connect(connection_url, { ...})
mongoose.connection.once('open', () => {
console.log('DB Connected')
const changeStream = mongoose.connection.collection('posts').watch()
changeStream.on('change', change => {
console.log(change)
if(change.operationType === "insert") {
console.log('Trigerring Pusher')
pusher.trigger('posts','inserted', {
change: change
})
} else {
console.log('Error trigerring Pusher')
}
})
})
app.get("/", (req, res) => res.status(200).send("Hello TheWebDev"))
...
//Listener
app.listen(port, () => console.log(`Listening on localhost: ${port}`))为了测试这一点,您需要从前端上传一个新的图像。同时,你需要在调试控制台中推料。
图 5-25 显示了调试控制台日志中显示的消息。
图 5-25
推进计程仪
是时候移动到前端使用 Pusher 了。首先,你需要在photo-social-frontend文件夹中安装pusher-js包。
npm i pusher-js从 www.pusher.com 获取代码放入 app 前端。导入推动器,然后使用App.js文件中的代码,这里有一个新的用于推动器的useEffect()钩。更新的内容用粗体标记。
...
import Pusher from 'pusher-js'
const pusher = new Pusher('56xxxxxxxxxxxxxxxx', {
cluster: 'ap2'
});
function App() {
...
const fetchPosts = async () => {
await axios.get("/sync").then(response => setPosts(response.data))
}
useEffect(() => {
const channel = pusher.subscribe('posts');
channel.bind('inserted', (data) => {
fetchPosts()
});
}, [])
useEffect(() => {
fetchPosts()
},[])
...
return (
<div className="app">
...
</div>
);
}
export default App;去找邮递员并发送另一个邮寄请求。您可以在本地主机上看到控制台日志中的数据。应用已完成。无论何时你发布了什么,它都会实时显示出来。
您可以在将应用部署到 Heroku 或推送到 GitHub 之前隐藏秘密,这是一种最佳做法。使用以下命令将dotenv安装到photo-social-backend文件夹中。
npm i dotenv然后在photo-social-backend文件夹中创建一个.env文件,并将所有秘密添加到其中。
DB_CONN='mongodb+srv://admin:<password>@cluster0.giruc.mongodb.net/photoDB?retryWrites=true&w=majority'
PUSHER_ID="11xxxx"
PUSHER_KEY="56xxxxxxxxxxxxxxxxxx"
PUSHER_SECRET="90xxxxxxxxxxxxxxxxxxx"在server.js中,导入dotenv,然后使用其中的值来代替所有的秘密。
...
import Posts from './postModel.js'
import dotenv from 'dotenv';
//App Config
dotenv.config()
const app = express()
const port = process.env.PORT || 9000
const connection_url = process.env.DB_CONN
const pusher = new Pusher({
appId: process.env.PUSHER_ID,
key: process.env.PUSHER_KEY,
secret: process.env.PUSHER_SECRET,
cluster: "ap2",
useTLS: true
});
//Middleware
...在后端的.gitignore文件中添加.env文件。更新的内容用粗体标记。
node_modules
.env转到 www.heroku.com 部署后端。按照第 1 章的步骤创建一个名为照片-社交-后台的应用。
由于这次您有环境变量,您必须将它们添加到设置➤配置变量中。请注意,不要在按键周围加上任何引号,如图 5-26 所示。
图 5-26
Heroku 的环境变量
部署成功后,进入 https://photo-social-backend.herokuapp.com 。图 5-27 显示了正确的文本。
图 5-27
后端已部署
转到axios.js,将端点改为 https://photo-social-backend.herokuapp.com 。如果一切正常,你的应用应该可以运行了。
import axios from 'axios'
const instance = axios.create({
baseURL: " https://photo-social-backend.herokuapp.com "
})
export default instance是时候在 Firebase 中部署前端了。遵循与第 1 章相同的程序。完成此过程后,站点应处于活动状态并正常工作,如图 5-28 所示。
图 5-28
最终应用
在这一章中,你创建了一个简单而实用的基于照片的社交网络。Firebase 在网上主办的。您学习了添加电子邮件身份验证,通过它您可以使用电子邮件登录。您还了解了如何在 Firebase 中存储图像,以及如何使用 Node.js 创建的 API 路由在 MongoDB 数据库中存储图像和文章的链接。



























