如何使用 Firebase 身份验证和数据库构建 Vue 调查应用程序
已发表: 2022-03-10
快速总结↬本教程将逐步指导您使用 Vue.js 和 Firebase 构建功能性调查应用程序。 从通过 Vuelidate 验证用户数据,到身份验证、存储用户数据、路由保护以及将数据发送到 Firebase 服务器。 本教程中使用的所有步骤都是实用的,并且可以在任何实际项目中重现,即使使用自定义后端也是如此。
在本教程中,您将构建一个调查应用程序,我们将在其中学习验证用户表单数据,在 Vue 中实现身份验证,并能够使用 Vue 和 Firebase(一个 BaaS 平台)接收调查数据。
在构建此应用程序时,我们将学习如何处理不同类型数据的表单验证,包括联系后端以检查是否已接收电子邮件,甚至在用户在注册期间提交表单之前。
此外,该应用程序将使用 RESTful API 处理用户的登录。 它将利用 Vue 路由器中的 Authguard 来阻止未登录的用户访问调查表,并将已登录用户的调查数据成功发送到安全数据库。
就这样我们在同一页面上,让我们澄清一下 Firebase 是什么,以及它将在本教程中做什么。 Firebase 是一个用于“构建、改进和发展您的应用程序”的工具集,它使您可以访问开发人员通常必须自己构建但并不真正想要构建的大部分服务,因为他们更愿意专注于应用体验本身。 这包括分析、身份验证、数据库、文件存储等内容,不胜枚举。
这与传统的应用程序开发不同,后者通常涉及编写前端和后端软件。 前端代码只是调用后端公开的 API 端点,而后端代码实际完成工作。 然而,对于 Firebase 产品,传统的后端被绕过了,将工作交给了客户端。 这在技术上允许像我这样的前端工程师只编写前端代码来构建全栈应用程序。
跳跃后更多! 继续往下看↓ 最重要的是,Firebase 将作为我们在这个项目中的后端,为我们提供必要的 API 端点来处理我们的身份验证和数据库需求。 最后,您将使用 Vue+ Firebase 构建一个功能性调查应用程序。 之后,您可以继续使用这些相同的流程构建您选择的任何 Web 应用程序,即使使用自定义后端也是如此。
接下来,您需要在您的机器上安装 Node 和 npm/yarn。 如果您还没有这样做,请按照这些快速指南在您的机器上安装 yarn 或 npm。 您还需要对本教程的 Vue、Vuex 和 Vue 路由器语法有基本的了解。
本教程的起始文件就在此处,其中包含该项目的基本文件,这里是已完成演示的 repo。 您可以克隆或下载存储库并在终端中运行npm install
。
安装启动文件后,您将看到一个欢迎页面,其中包含注册和登录的选项。登录后,您可以访问调查。
如果您想完全自己构建这个项目,请随意创建一个新项目,只需确保将 Vuex、Vue 路由器、Vuelidate 和 axios 安装到您的 Vue 项目中。 所以让我们直接进入:
首先,我们需要一个 Firebase 帐户来设置这个项目,这非常类似于为我们的应用程序创建一个容器,让我们能够访问数据库、各种身份验证方式、托管等。一旦你设置它就很简单'在 Firebase 网站上。
现在我们有了项目,接下来就是在 Firebase 上设置我们的身份验证系统和数据库(实时数据库)。
- 点击“认证”选项;
- 设置我们想要的“登录方法”(在本例中为电子邮件/密码)。
- 点击“数据库”。
- 选择“实时数据库”并复制顶部的此链接。
当我们想要将数据发送到我们的 firebase 数据库时,它作为 API 端点将非常有用。
我们将此 API 称为数据库 API。 要使用它,您必须在发送时添加您选择的数据库的名称。 例如,要发送到一个名为 user 的数据库。 您只需在最后添加user.json :
{databaseAPI}/user.json
在此之后,我们将转到 Firebase auth rest API 文档以获取我们的注册并登录 API 端点。 在这些端点中,将需要我们项目的 API 密钥,它可以在我们的项目设置中找到。
验证
回到我们的代码,在发送到服务器之前会验证注册数据,以确保用户发送适当的信息。 我们将使用 Vuelidate,这是一个很酷的库,可以让 Vue 中的验证更容易。 首先,将Vuelidate安装到项目中:
npm i vuelidate
转到src/components/auth/signup.vue
并在脚本标签中导入 vuelidate 以及我们需要从库中获取的所有必要事件,如下所示。
注意:您可以查看文档以获取有关库和所有可用事件的完整概述。
import { required, email, numeric, minValue, minLength, sameAs } from 'vuelidate/lib/validators'
快速解释:
价值 | 描述
---|
required | 该值是强制性的 |
email | 值必须是电子邮件 |
numeric | 必须是数字 |
minValue | 用户可以输入的最小数值。 |
sameAs | 用于比较两个值以确保它们相同 |
同时导入 [`axios`](https://github.com/axios/axios) 以便能够向服务器发送 HTTP 请求:
import axios from 'axios'
在我们继续之前,我们需要向数据库添加一些规则,以便能够按我们应该的方式验证电子邮件,如下所示:
"read" = "true"
这意味着可以在客户端不受任何阻碍地读取数据库。
"write" = "auth" !== null
除非您是经过身份验证的用户,否则您不能在数据库上写入。
"Users" = { "onIndex" : ["email"] }
这允许我们查询带有 `email` 索引的 `users` 文档。 也就是说,您可以从字面上过滤数据库以获得唯一的电子邮件。 然后添加一个名为 `validations` 的自定义计算属性,就像我们有方法、计算等一样。在 `validations` 下,我们将有方法来验证从需要的 `email` 开始的必要数据,显然必须是电子邮件. 此外,我们希望能够在电子邮件已被其他人接收时告诉用户,方法是在用户键入后使用自定义验证器中的异步验证器检查数据库,并且所有这些都由 [vuelidate. ](https://vuelidate.js.org/#sub-asynchronous-validation)
validations : { email: { required, email, unique: val => { if (val === '') return true return axios.get('https://vue-journal.firebaseio.com/users.json?orderBy="email"&equalTo="' + val + '"') .then(res => { return Object.keys(res.data).length === 0 }) } } }
然后在唯一下,使用 axios 查询数据库并使用默认的 Object.keys 仅当它的长度为 0 时才返回响应。对于年龄,您将添加必需的、数字的和分配给 `minVal 的最小值 18 ` 作为它的属性。
age: { required, numeric, minVal: minValue(18) }
密码的属性是必需的,分配给 `minLen` 的最小长度为 6。
password: { required, minLen: minLength(6) }
`confirmPassword` 属性基本上要和密码一样。
confirmPassword: { sameAs: sameAs(vm => { return vm.password }) }
要告诉用户该电子邮件已被占用,请使用 `v-if` 来检查 `unique` 是真还是假。 如果为 true,则表示返回的 Object 的长度为 0,仍然可以使用 email,反之亦然。 以同样的方式,您可以使用 `v-if` 检查用户输入是否是实际的电子邮件。 对于单个输入上的所有周围 div,我们将添加一个无效类,一旦该输入出现错误,该类就会变为活动状态。 要将验证事件绑定到 HTML 中的每个输入,我们使用 [`$touch()`](https://vuelidate.js.org/#sub-without-v-model),如 `email `下面。
<div class="input" :class="{invalid: $v.email.$error}"> <h6 v-if="!$v.email.email">Please provide a valid email address.</h6> <h6 v-if="!$v.email.unique">This email address has been taken.</h6> <input type="email" placeholder="Email" @blur="$v.email.$touch()" v-model="email"> </div>
`Age`、`password` 和 `confirmPassword` 将以与 `email` 类似的方式绑定到它们的 HTML 输入。 如果任何输入出现错误,我们将使“提交”按钮处于非活动状态。
<button type="submit" :disabled="$v.$invalid">create</button>
这是此 vuelidate 部分的完整 [CodePen 示例](https://codepen.io/atanda1/pen/Yzyqrjv)。
## Authentication 这个应用程序是一个 SPA,不会像传统网站那样重新加载,所以我们将使用 Vuex 作为我们的单一“事实来源”,让我们应用程序中的每个组件都知道一般的身份验证状态。 我们转到我们的商店文件,并在操作中创建登录/注册方法。 当我们发送用户数据时收到的响应(`token` 和 `userId`)将存储在我们的状态中。 这很重要,因为令牌将用于了解我们在应用程序中的任何时候是否仍在登录。 `token`、`userId` 和 user 在初始值为 null 的状态下创建。 我们将在很晚的时候接触到用户,但现在,我们将专注于前两个。
state: { idToken: null, userId: null, user: null }
然后创建突变以在需要时更改状态。
authUser | 保存令牌和用户userId |
storeUser | 存储用户信息 |
clearAuthData | 将数据擦除回初始状态 |
mutations: { authUser (state, userData) { state.idToken = userData.token state.userId = userData.userId }, storeUser (state, user) { state.user = user }, clearAuthData (state) { state.idToken = null state.userId = null state.user = null } }
对于注册/登录,我们必须为两者创建单独的操作,我们将身份验证请求发送到服务器。 之后,我们来自注册/登录的响应(令牌和用户 ID)被提交给 authUser,并保存在本地存储中。
signup ({commit, dispatch}, authData) { axios.post('https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyCFr-OMMzDGp4Mmr0t66w2cTGfNazYjptQ', { email: authData.email, password: authData.password, returnSecureToken: true }) .then(res => { console.log(res) commit('authUser', { token: res.data.idToken, userId: res.data.localId }) localStorage.setItem('token', res.data.idToken) localStorage.setItem('userId', res.data.localId) localStorage.setItem('email', res.data.email) dispatch('storeUser', authData) setTimeout(function () { router.push('/dashboard') }, 3000) }) .catch(error => console.log(error)) }
login ({commit}, authData) { axios.post('https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=AIzaSyCFr-OMMzDGp4Mmr0t66w2cTGfNazYjptQ', { email: authData.email, password: authData.password, returnSecureToken: true }) .then(res => { console.log(res) localStorage.setItem('token', res.data.idToken) localStorage.setItem('userId', res.data.localId) localStorage.setItem('email', res.data.email) commit('authUser', { token: res.data.idToken, userId: res.data.localId }) router.push('/dashboard') }) .catch(error => console.log(error.message)) }
但这里有一个棘手的部分,我们将对注册操作做的特别是只发送要在身份验证数据库中注册的电子邮件和密码。 实际上,我们无权使用此身份验证数据库中的数据,并且除了电子邮件/密码之外,我们没有发送任何注册数据。 所以我们要做的是创建另一个动作来将完整的注册数据发送到另一个数据库。 在这个单独的数据库文档中,我们可以完全访问我们选择保存在那里的所有信息。 我们将把这个新动作称为 `storeUser` 我们然后转到我们的注册操作并将包含我们的注册数据的整个对象分派到我们现在可以通过 `storeUser` 访问的数据库。 **注意:**出于安全原因,您可能不希望将用户密码与 `storeUser` 一起发送到数据库。
storeUser ({ state}, userData) { if (!state.idToken) { return } axios.post('https://vue-journal.firebaseio.com/users.json' + '?auth=' + state.idToken, userData) .then(res => console.log(res)) .catch(error => console.log(error)) } }
`storeUser` 在发布到数据库时使用我们新获得的令牌和数据库 API 添加查询。 这是因为我们无法写入我们的数据库,除非我们使用我们的证明(令牌)进行了身份验证。 这就是我们一开始给 Firebase 的规则,记得吗?
“write” = “auth” !== null
注册/登录操作的完整代码就在 [这里](https://codepen.io/atanda1/pen/mdePKqj)。 然后在 `onSubmit` 方法中将注册和登录从它们的组件分派到商店中的相应操作。
methods : { onSubmit () { const signupData = { email : this.email, name : this.name, age : this.age, password : this.password, confirmPassword : this.co nfirmPassword } this.$store.dispatch('signup', signupData) } } }
**注意:** `signupData` 包含表单的数据。
methods : { onSubmit = { const formData = { email : this.email, password : this.password } this.$store.dispatch('login', {email: formData.email, password: formData.password}) } }
## AuthGuard 需要 AuthGuard 来防止未登录的用户访问他们将发送调查的仪表板。 转到路由文件并导入我们的商店。
import store from './store'
在路由中,转到仪表板的路径并添加以下内容:
const routes = [ { path: '/', component: WelcomePage }, { path: '/signup', component: SignupPage }, { path: '/signin', component: SigninPage }, { path: '/dashboard', component: DashboardPage, beforeEnter (to, from, next) { if (store.state.idToken) { next() } else { next('/signin') } } } ]
所有这一切都是为了检查状态中是否存在令牌,如果是,我们授予对仪表板的访问权限,反之亦然。 ## LogOut 为了创建我们的注销选项,我们将使用我们之前在 `mutations` 下创建的 `clearAuth`,它只是将 `token` 和 `userId` 设置为 `null`。 我们现在创建一个新的 `logout` `action` ,提交到 `clearAuth`,删除本地存储并添加 `router.replace('/')` 以完全重定向用户。
actions: { logout ({commit}) { commit('clearAuth') localStorage.removeItem('token') localStorage.removeItem('userId') router.replace('/') } }
在标头组件中,我们有一个 `onLogout` 方法,它在商店中调度我们的注销操作。
methods: { onLogout() { this.$store.dispatch('logout') } }
然后,我们向触发 `onLogout` 方法的按钮添加一个`@click`,我们可以在 [这里](https://codepen.io/atanda1/pen/jObqKNd) 中看到。
<ul @click="onLogout">Log Out</ul>
## UI_State 现在我们已经授予了对仪表板的条件访问权限,下一步是将其从导航栏中删除,因此只有经过身份验证的用户才能查看它。 为此,我们将在 `getters` 下添加一个名为 `ifAuthenticated` 的新方法,该方法检查我们状态中的令牌是否为空。 当有令牌时,它表明用户已通过身份验证,我们希望他们在导航栏上看到调查仪表板选项。
getters: { isAuthenticated (state) { return state.idToken !== null } }
之后,您返回到 header 组件并在 computed 下创建一个方法 `auth`,该方法在我们刚刚在 store 中创建的 `getter` 中分派给我们的 `isAuthenticated`。 这样做是如果没有令牌,`isAuthenticated` 将返回 false,这意味着 `auth` 也将为 null,反之亦然。
computed: { auth () { return this.$store.getters.ifAuthenticated } }
之后,我们在 HTML 中添加一个 `v-if` 来检查 `auth` 是否为空,确定该选项是否会显示在导航栏上。
<li v-if='auth'> <router-link to="/dashboard">Dashboard</router-link> </li> <li v-if='!auth'> <router-link to="/signup">Register</router-link> </li> <li v-if='!auth'> <router-link to="/signin">Log In</router-link> </li>
- 您将在 [此处](https://codepen.io/atanda1/pen/QWjNxyo) 找到 UI 状态部分的完整代码。
自动登录
当我们重新加载我们的应用程序时,我们会丢失数据并退出,不得不重新开始。 这是因为我们的 token 和 Id 存储在 Vuex 中,它是 javascript,这意味着我们的应用程序在刷新时会通过浏览器重新加载。
最后,我们要做的是在本地存储中检索令牌。 通过这样做,无论何时刷新窗口,我们都可以在浏览器上拥有用户的令牌,并且在令牌仍然有效的情况下,有一个方法自动登录我们的用户。
创建了一个名为AutoLogin
的新actions
方法,我们将从本地存储中获取令牌和userId
,并将我们的数据提交给突变中的authUser
方法。
actions : { AutoLogin ({commit}) { const token = localStorage.getItem('token') if (!token) { return } const userId = localStorage.getItem('userId') const token = localStorage.getItem('token') commit('authUser', { idToken: token, userId: userId }) } }
然后我们去我们的 App.vue 并编写一个created
方法,每次加载应用程序时都会从我们的商店调度autoLogin
。
created () { this.$store.dispatch('AutoLogin') }
Fetch_User_Data
我们希望通过显示用户名来欢迎仪表板上的用户。 因此,创建了另一个名为fetchUser
的操作,它首先像往常一样检查是否有令牌。 然后,它继续从本地存储中获取电子邮件,并像之前通过电子邮件验证所做的那样查询数据库。
这将返回一个对象,其中包含最初在注册期间提交的用户数据。 然后我们将此对象转换为一个数组并将其提交给最初创建的storeUser
突变。
fetchUser ({ commit, state}) { if (!state.idToken) { return } const email = localStorage.getItem('email') axios.get('https://vue-journal.firebaseio.com/users.json?orderBy="email"&equalTo="' + email + '"') .then(res => { console.log(res) // const users = [] console.log(res.data) const data = res.data const users = [] for (let key in data) { const user = data[key] user.id = key users.push(user) console.log(users) } commit('storeUser', users[0]) }) .catch(error => console.log(error)) }
之后,我们创建另一个名为user
的 getter,它返回已经通过storeUser
提交的state.user
。
getters: { user (state) { return state.user }, isAuthenticated (state) { return state.idToken !== null } }
回到仪表板,我们创建一个名为name
的新计算方法,仅当用户存在时才返回state.user.name
。
computed: { name () { return !this.$store.getters.user ? false : this.$store.getters.user.name } }, created () { this.$store.dispatch('fetchUser') } }
我们还将添加created
计算属性以在页面加载后调度fetchUser
操作。 然后,我们在 HTML 中使用v-if
来显示名称(如果名称存在)。
<p v-if="name">Welcome, {{ name }} </p>
发送_调查
为了发送调查,我们将创建一个postData
操作,该操作使用数据库 API 将数据发送到数据库,并使用令牌显示用户已登录的服务器。
postData ({state}, surveyData) { if (!state.idToken) { return } axios.post('https://vue-journal.firebaseio.com/survey.json' + '?auth=' + state.idToken , surveyData) .then(res => { console.log(res) }) .catch(error => console.log(error)) }
我们回到仪表板组件并将数据分派到存储中的postData
操作。
methods : { onSubmit () { const postData = { price: this.price, long: this.long, comment: this.comment } console.log(postData) this.$store.dispatch('postData', postData) } }
有了它,我们在演示应用程序中实现了许多有用的功能,同时与我们的 Firebase 服务器通信。 希望您将在下一个项目中使用这些强大的功能,因为它们对于当今构建现代 Web 应用程序非常重要。
如果您有任何问题,可以在评论部分留下它们,我很乐意为您一一解答!
其他可能被证明有用的资源包括:
- 要了解有关 Firebase 及其提供的其他服务的更多信息,请查看 Chris Esplin 的文章“什么是 Firebase?”
- Vuelidate 是一个非常不错的库,你应该深入研究一下。 你应该通读它的文档以获得完整的洞察力。https://vuelidate.js.org/。
- 您也可以自行探索 axios,特别是如果您想在更大的项目中使用它。