快速入门|使用MemFire Cloud构建React Native应用程序

艾木酱
• 阅读 262

MemFire Cloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专注于编写前端应用程序代码,加速WEB或APP应用开发。

视频教学链接

此示例提供了使用 MemFire Cloud 和 React Native构建简单用户管理应用程序(从头开始)的步骤。这包括:

  • MemFire Cloud云数据库:用于存储用户数据的 MemFireDB数据库。

  • MemFire Cloud用户验证:用户可以使用邮箱密码登录。

  • MemFire Cloud存储:用户可以上传照片。

  • 行级安全策略:数据受到保护,因此个人只能访问自己的数据。

  • 即时API:创建数据库表时会自动生成 API。

在本指南结束时,您将拥有一个允许用户登录和更新一些基本个人资料详细信息的应用程序:

快速入门|使用MemFire Cloud构建React Native应用程序

创建应用

目的:我们的应用就是通过在这里创建的应用来获得数据库、云存储等一系列资源,并将获得该应用专属的API访问链接和访问密钥,用户可以轻松的与以上资源进行交互。

登录https://cloud.memfiredb.com/auth/login创建应用

快速入门|使用MemFire Cloud构建React Native应用程序

创建数据表

点击应用,视图化创建数据表

  1. 创建profiles表;

在数据表页面,点击“新建数据表”,页面配置如下:

快速入门|使用MemFire Cloud构建React Native应用程序

其中profiles表字段id和auth.users表中的id字段(uuid类型)外键关联。

  1. 开启Profiles的RLS数据安全访问规则;

选中创建的Profiles表,点击表权限栏,如下图所示,点击"启用RLS"按钮

快速入门|使用MemFire Cloud构建React Native应用程序

  1. 允许每个用户可以查看公共的个人信息资料;

点击"新规则"按钮,在弹出弹框中,选择"为所有用户启用访问权限",输入策略名称,选择"SELECT(查询)"操作,点击“创建策略”按钮,如下图。

快速入门|使用MemFire Cloud构建React Native应用程序

  1. 仅允许用户增删改查本人的个人资料信息;

点击"新规则"按钮,在弹出弹框中,选择"根据用户ID为用户启用访问权限",输入策略名称,选择"ALL(所有)"操作,点击“创建策略”按钮,如下图。

快速入门|使用MemFire Cloud构建React Native应用程序

创建avatars存储桶

创建云存储的存储桶,用来存储用户的头像图片,涉及操作包括:

  1. 创建一个存储桶avatars

在该应用的云存储导航栏,点击“新建Bucket”按钮,创建存储桶avatars。

快速入门|使用MemFire Cloud构建React Native应用程序

  1. 允许每个用户可以查看存储桶avatars

选中存储桶avatars,切换到权限设置栏,点击“新规则”按钮,弹出策略编辑弹框,选择“自定义”,如下图所示:

快速入门|使用MemFire Cloud构建React Native应用程序

选择SELECT操作,输入策略名称,点击“生成策略”按钮,如下图所示。

快速入门|使用MemFire Cloud构建React Native应用程序

  1. 允许用户上传存储桶avatars;

选中存储桶avatars,切换到权限设置栏,点击“新规则”按钮,弹出策略编辑弹框,选择“自定义”,如下图所示:

快速入门|使用MemFire Cloud构建React Native应用程序

选择INSERT操作,输入策略名称,点击“生成策略”按钮,如下图所示。

快速入门|使用MemFire Cloud构建React Native应用程序

查看结果

快速入门|使用MemFire Cloud构建React Native应用程序

所有数据表及RLS的sql(策略名称用英文代替)

-- Create a table for public "profiles"
create table profiles (
  id uuid references auth.users not null,
  updated_at timestamp with time zone,
  username text unique,
  avatar_url text,
  website text,

  primary key (id),
  unique(username),
);

alter table profiles enable row level security;

create policy "Public profiles are viewable by everyone."
  on profiles for select
  using ( true );

create policy "Users can insert their own profile."
  on profiles for insert
  with check ( auth.uid() = id );

create policy "Users can update own profile."
  on profiles for update
  using ( auth.uid() = id );
-- Set up Storage!
insert into storage.buckets (id, name)
values ('avatars', 'avatars');

create policy "Avatar images are publicly accessible."
  on storage.objects for select
  using ( bucket_id = 'avatars' );

create policy "Anyone can upload an avatar."
  on storage.objects for insert
  with check ( bucket_id = 'avatars' );

获取 API密钥

现在您已经创建了一些数据库表,您可以使用自动生成的 API 插入数据。我们只需要从API设置中获取您在上面复制的URL和anon的密钥。

在应用->概括页面,获取服务地址以及token信息。

快速入门|使用MemFire Cloud构建React Native应用程序

Anon(公开)密钥是客户端API密钥。它允许“匿名访问”您的数据库,直到用户登录。登录后,密钥将切换到用户自己的登录令牌。这将为数据启用行级安全性。

注意:service_role(秘密)密钥可以绕过任何安全策略完全访问您的数据。这些密钥必须保密,并且要在服务器环境中使用,绝不能在客户端或浏览器上使用。 在后续示例代码中,需要提供supabaseUrl和supabaseKey。

认证设置

当用户点击邮件内魔法链接进行登录时,是需要跳转到我们应用的登录界面的。这里需要在认证设置中进行相关URL重定向的配置。

因为我们最终的应用会在本地的3000端口启动(亦或者其他端口),所以这里我们暂时将url设置为http://localhost:3000/

除此之外,在此界面也可以自定义使用我们自己的smtp服务器。

快速入门|使用MemFire Cloud构建React Native应用程序

构建应用程序

让我们从头开始构建 React Native应用程序。

初始化 React Native

我们可以使用create-expo-app来初始化一个名为 memfiredbReactNative:

npx create-expo-app memfiredbReactNative
cd memfiredbReactNative

然后让我们安装额外的依赖项:supabase-js

yarn add @supabase/supabase-js
yarn add react-native-elements
yarn add react-native-safe-area-context
yarn add @react-native-async-storage/async-storage
yarn add react-native-url-polyfill

我们需要创建一个可以访问我们应用程序数据的客户端,我们使用了Supabase 客户端,使用他生态里提供的功能(登录、注册、增删改查等)去进行交互。创建一个可以访问我们应用程序数据的客户端需要接口的地址(URL)和一个数据权限的令牌(ANON_KEY),我们需要去应用的概览里面去获取这两个参数然后配置到supabase.js里面去。

lib/supabase.js文件

import AsyncStorage from '@react-native-async-storage/async-storage';
import { createClient } from '@supabase/supabase-js'

const URL = ""
const ANON_KEY = ""

export const supabase = createClient(URL, ANON_KEY, {
  localStorage: AsyncStorage,
  autoRefreshToken: true,
  persistSession: true,
  detectSessionInUrl: false,
});

让我们设置一个 React Native 组件来管理登录和注册。用户将能够使用他们的电子邮件和密码登录。

components/Auth.js

import React, { useState } from 'react'
import { Alert, StyleSheet, View } from 'react-native'
import { supabase } from '../lib/supabase'
import { Button, Input } from 'react-native-elements'

export default function Auth() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [loading, setLoading] = useState(false)

  async function signInWithEmail() {
    setLoading(true)
    const { user, error } = await supabase.auth.signIn({
      email: email,
      password: password,
    })

    if (error) Alert.alert(error.message)
    setLoading(false)
  }

  async function signUpWithEmail() {
    setLoading(true)
    const { user, error } = await supabase.auth.signUp({
      email: email,
      password: password,
    })

    if (error) Alert.alert(error.message)
    setLoading(false)
  }

  return (
    <View style={styles.mt100}>
      <View style={[styles.verticallySpaced, styles.mt20]}>
        <Input
          label="邮箱"
          leftIcon={{ type: 'font-awesome', name: 'envelope' }}
          onChangeText={(text) => setEmail(text)}
          value={email}
          placeholder="email@address.com"
          autoCapitalize={'none'}
        />
      </View>
      <View style={styles.verticallySpaced}>
        <Input
          label="密码"
          leftIcon={{ type: 'font-awesome', name: 'lock' }}
          onChangeText={(text) => setPassword(text)}
          value={password}
          secureTextEntry={true}
          placeholder="Password"
          autoCapitalize={'none'}
        />
      </View>
      <View style={[styles.verticallySpaced, styles.mt20]}>
        <Button title="登录" disabled={loading} onPress={() => signInWithEmail()} />
      </View>
      <View style={styles.verticallySpaced}>
        <Button title="注册" disabled={loading} onPress={() => signUpWithEmail()} />
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    marginTop: 40,
    padding: 12,
  },
  verticallySpaced: {
    paddingTop: 4,
    paddingBottom: 4,
    alignSelf: 'stretch',
  },
  mt20: {
    marginTop: 20,
  },
  mt100:{
    marginTop:100
  }
})

用户信息页面

用户登录后,我们可以允许他们编辑他们的个人资料详细信息并管理他们的帐户。

让我们为它创建一个名为Account.js.

components/Account.js

import { useState, useEffect } from "react";
import { supabase } from "../lib/supabase";
import { StyleSheet, View, Alert } from "react-native";
import { Button, Input } from "react-native-elements";
import { ApiError, Session } from "@supabase/supabase-js";

export default function Account({ session }) {
  const [loading, setLoading] = useState(false);
  const [username, setUsername] = useState("");
  const [website, setWebsite] = useState("");
  const [avatar_url, setAvatarUrl] = useState("");

  useEffect(() => {
    if (session) getProfile();
  }, [session]);

  async function getProfile() {
    try {
      setLoading(true);
      const user = supabase.auth.user();
      if (!user) throw new Error("No user on the session!");

      let { data, error, status } = await supabase
        .from("profiles")
        .select(`username, website, avatar_url`)
        .eq("id", user.id)
        .single();
      if (error && status !== 406) {
        throw error;
      }

      if (data) {
        setUsername(data.username);
        setWebsite(data.website);
        setAvatarUrl(data.avatar_url);
      }
    } catch (error) {
      Alert.alert((error).message);
    } finally {
      setLoading(false);
    }
  }

  async function updateProfile({
    username,
    website,
    avatar_url,
  }) {
    try {
      setLoading(true);
      const user = supabase.auth.user();
      if (!user) throw new Error(" 会话中没有用户!");

      const updates = {
        id: user.id,
        username,
        website,
        avatar_url,
        updated_at: new Date(),
      };

      let { error } = await supabase
        .from("profiles")
        .upsert(updates, { returning: "minimal" });

      if (error) {
        throw error;
      }
    } catch (error) {
      Alert.alert((error).message);
    } finally {
      setLoading(false);
    }
  }

  return (
    <View style={styles.mt100}>
        <View>

        </View>
      <View style={[styles.verticallySpaced, styles.mt20]}>
        <Input label="邮箱" value={session?.user?.email} disabled />
      </View>
      <View style={styles.verticallySpaced}>
        <Input
          label="用户名"
          value={username || ""}
          onChangeText={(text) => setUsername(text)}
        />
      </View>
      <View style={styles.verticallySpaced}>
        <Input
          label="网址"
          value={website || ""}
          onChangeText={(text) => setWebsite(text)}
        />
      </View>

      <View style={[styles.verticallySpaced, styles.mt20]}>
        <Button
          title={loading ? "正在修改" : "确定"}
          onPress={() => updateProfile({ username, website, avatar_url })}
          disabled={loading}
        />
      </View>

      <View style={styles.verticallySpaced}>
        <Button title="登出" onPress={() => supabase.auth.signOut()} />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    marginTop: 40,
    padding: 12,
    backgroundColor:'black'
  },
  verticallySpaced: {
    paddingTop: 4,
    paddingBottom: 4,
    alignSelf: "stretch",
  },
  mt20: {
    marginTop: 20,
  },
  mt100:{
    marginTop:100
  }
});

修改项目入口文件

现在我们已经准备好所有组件,让我们更新App.js:

App.js

import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
import 'react-native-url-polyfill/auto'
import React, { useRef, useState, useEffect } from 'react';
import { supabase } from './lib/supabase'
import Auth from './components/Auth'
import Account from './components/Account'

export default function App() {
  const [session, setSession] = useState()

  useEffect(() => {
    setSession(supabase.auth.session())

    supabase.auth.onAuthStateChange((_event, session) => {
      setSession(session)
    })
  }, [])

  return (
    <View>
      {session && session.user ? <Account key={session.user.id} session={session} /> : <Auth />}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'black',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

完成后,在终端执行打开浏览器

expo start --web

快速入门|使用MemFire Cloud构建React Native应用程序

实现:上传头像及更新用户信息

每个 MemFire Cloud项目都配置了存储,用于管理照片和视频等大文件。

创建上传小组件

让我们为用户创建一个头像,以便他们可以上传个人资料照片。

创建上传头像的小组件

components/Avatar.js

import { useEffect, useState } from 'react'
import { supabase } from '../lib/supabase'
import { View } from 'react-native'
import { Button } from 'react-native-elements'

export default function Avatar({ url, size, onUpload }) {
  const [avatarUrl, setAvatarUrl] = useState(null)
  const [uploading, setUploading] = useState(false)

  useEffect(() => {
    if (url) downloadImage(url)
  }, [url])

  async function downloadImage(path) {
    try {
      const { data, error } = await supabase.storage.from('avatars').download(path)
      if (error) {
        throw error
      }
      var blob = new Blob([data], {
        type: "text/vtt; charset=utf-8"
    });

    const fileReaderInstance = new FileReader();
    fileReaderInstance.readAsDataURL(blob);
    fileReaderInstance.onload = () => {
        let base64 = fileReaderInstance.result;
        setAvatarUrl(base64)
    }
    } catch (error) {
      console.log('Error downloading image: ', error.message)
    }
  }
  async function uploadAvatar(event) {
    try {
      setUploading(true)

      if (!event.target.files || event.target.files.length === 0) {
        throw new Error('You must select an image to upload.')
      }

      const file = event.target.files[0]
      const fileExt = file.name.split('.').pop()
      const fileName = `${Math.random()}.${fileExt}`
      const filePath = `${fileName}`

      let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)
      if (uploadError) {
        throw uploadError
      }

      onUpload(filePath)
    } catch (error) {
      alert(error.message)
    } finally {
      setUploading(false)
    }
  }

  return (
    <View>
      {avatarUrl ? (
        <img src={avatarUrl} alt="Avatar" className="avatar image" />
      ) : (
        <View className="avatar no-image" />
      )}
      <View>
      <label className="button primary block" htmlFor="single">
        <Button  title={uploading ? 'Uploading ...' : 'Upload'}></Button>

        </label>
      <input
          style={{
            visibility: 'hidden',
            position: 'absolute',
          }}
          type="file"
          id="single"
          accept="image/*"
          onChange={uploadAvatar}
          disabled={uploading}
        />
      </View>
    </View>
  )
}

然后我们可以在components/Account.js模板的顶部添加小部件:

components/Account.js

import { useState, useEffect } from "react";
import { supabase } from "../lib/supabase";
import { StyleSheet, View, Alert } from "react-native";
import { Button, Input } from "react-native-elements";
import { ApiError, Session } from "@supabase/supabase-js";
import Avatar from './Avatar'

export default function Account({ session }) {
  const [loading, setLoading] = useState(false);
  const [username, setUsername] = useState("");
  const [website, setWebsite] = useState("");
  const [avatar_url, setAvatarUrl] = useState("");

  useEffect(() => {
    if (session) getProfile();
  }, [session]);

  async function getProfile() {
    try {
      setLoading(true);
      const user = supabase.auth.user();
      if (!user) throw new Error("No user on the session!");

      let { data, error, status } = await supabase
        .from("profiles")
        .select(`username, website, avatar_url`)
        .eq("id", user.id)
        .single();
      if (error && status !== 406) {
        throw error;
      }

      if (data) {
        setUsername(data.username);
        setWebsite(data.website);
        setAvatarUrl(data.avatar_url);
      }
    } catch (error) {
      Alert.alert((error).message);
    } finally {
      setLoading(false);
    }
  }

  async function updateProfile({
    username,
    website,
    avatar_url,
  }) {
    try {
      setLoading(true);
      const user = supabase.auth.user();
      if (!user) throw new Error(" 会话中没有用户!");

      const updates = {
        id: user.id,
        username,
        website,
        avatar_url,
        updated_at: new Date(),
      };

      let { error } = await supabase
        .from("profiles")
        .upsert(updates, { returning: "minimal" });

      if (error) {
        throw error;
      }
    } catch (error) {
      Alert.alert((error).message);
    } finally {
      setLoading(false);
    }
  }

  return (
    <View style={styles.mt100}>
        <View>
        <Avatar url={avatar_url}
      size={150}
      onUpload={(url) => {
        setAvatarUrl(url)
        updateProfile({ username, website, avatar_url: url })
      }}/>
        </View>
      <View style={[styles.verticallySpaced, styles.mt20]}>
        <Input label="邮箱" value={session?.user?.email} disabled />
      </View>
      <View style={styles.verticallySpaced}>
        <Input
          label="用户名"
          value={username || ""}
          onChangeText={(text) => setUsername(text)}
        />
      </View>
      <View style={styles.verticallySpaced}>
        <Input
          label="网址"
          value={website || ""}
          onChangeText={(text) => setWebsite(text)}
        />
      </View>

      <View style={[styles.verticallySpaced, styles.mt20]}>
        <Button
          title={loading ? "正在修改" : "确定"}
          onPress={() => updateProfile({ username, website, avatar_url })}
          disabled={loading}
        />
      </View>

      <View style={styles.verticallySpaced}>
        <Button title="登出" onPress={() => supabase.auth.signOut()} />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    marginTop: 40,
    padding: 12,
    backgroundColor:'black'
  },
  verticallySpaced: {
    paddingTop: 4,
    paddingBottom: 4,
    alignSelf: "stretch",
  },
  mt20: {
    marginTop: 20,
  },
  mt100:{
    marginTop:100
  }
});

恭喜!在这个阶段,您拥有一个功能齐全的应用程序!

点赞
收藏
评论区
推荐文章
艾木酱 艾木酱
4个月前
超轻量级的VSCode插件,数据库想用就用~
MemFireCloud是一个便捷、灵活、高效的云服务平台,依托于分布式数据库MemFireDB的云原生和线性扩展能力,为互联网用户提供一站式数据库自助服务。介绍MemFireCloud推出VSCode的可视化数据库管理插件,提供操作数据库的图形界面,帮助开发人员轻松简单的写代码,边操作数据库。其主要功能包括:连接管理:统一管理所有的数据库连接;
Stella981 Stella981
1年前
Serverless
  使用Baas和Faas是Serverless应用的基本特征,符合这两个基本特征的应用可称为Serverless应用。Baas(BackendasaService,后端即服务,使用了公共云提供的对象存储和数据库服务)。Faas(FunctionsasaService,函数即服务,使用Lambda框架)。AI应用用到了对象存储
Stella981 Stella981
1年前
Kerberos无约束委派的攻击和防御
 0x00前言简介当ActiveDirectory首次与Windows2000Server一起发布时,Microsoft就提供了一种简单的机制来支持用户通过Kerberos对Web服务器进行身份验证并需要授权用户更新后端数据库服务器上的记录的方案。这通常被称为Kerberosdoublehopissue(双跃点问题),
Wesley13 Wesley13
1年前
vivo 云服务海量数据存储架构演进与实践
一、写在开头vivo云服务提供给用户备份手机上的联系人、短信、便签、书签等数据的能力,底层存储采用MySQL数据库进行数据存储。随着vivo云服务业务发展,云服务用户量增长迅速,存储在云端的数据量越来越大,海量数据给后端存储带来了巨大的挑战。云服务业务这几年最大的痛点,就是如何解决用户海量数据的存储问题。二、面临挑战
Stella981 Stella981
1年前
OpenStack最新版本Victoria发布亮点与初体验
前言OpenStack是一个云操作系统,可控制整个数据中心内的大型计算,存储和网络资源池,所有资源均通过具有通用身份验证机制的API进行管理和配置。还提供了一个仪表板,可让管理员进行控制,同时授权其用户通过Web界面配置资源。除了标准的基础架构即服务功能外,其他组件还提供业务流程,故障管理和服务管理等其他服务,以确保用户应用程序的高可用性
Wesley13 Wesley13
1年前
Mysql用户与权限操作
1.用户与权限概述用户是数据库的使用者和管理者。MySQL通过用户的设置来控制数据库操作人员的访问与操作范围。服务器中名为mysqI的数据库,用于维护数据库的用户以及权限的控制和管理。MySQL中的所有用户信息都保存在mysql.user数据表中。根据my
Wesley13 Wesley13
1年前
mysql数据库,创建只读用户
数据库当前只有一个root用户,需要创建一个只读帐户给其他使用,因使用者是使用数据库管理工具在其他主机访问,所以还要开户远程访问权限,操作步骤如下。1\.使用现有的root用户登录到Mysql。mysqlurootp2. 创建用户,并授权SELECT查询权限,授权远程访问权限,注意,命令中username/password指用户
艾木酱 艾木酱
4个月前
快速入门|使用MemFire Cloud构建Flutter应用程序
MemFireCloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专注于编写前端应用程序代码,加速WEB或APP应用开发。此示例提供了使用MemFireCloud和Flut
艾木酱 艾木酱
4个月前
应用实战|微信小程序开发示例之Super课表
此示例提供了使用MemFireCloud构建一个课表的小程序的步骤。小程序用到的MemFireCloud的功能包括:云数据库:存储小程序数据表的信息。用户验证:小程序使用MemFireCloud提供的用户认证的API接口,快速完成用户注册登录操作。云存储:存储小程序的注册用户上传的头像。行级安全策略:采用RLS策略来限制用户访问行为,用户可
如何远程管理天翼云RDS数据库
天翼云MySQLRDS数据库当前仅允许从云主机内网访问,暂时不支持绑定公网IP地址远程访问和控制数据库。很多用户更习惯使用Windows上的图形客户端对数据库进行管理,如果有Windows云主机则可以远程桌面登录到Windows云主机上使用MySQLFront或Navicat等图形客户端访问MySQLRDS数据库,如果只有Linux云主机是不是就没有办法