重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
本篇内容介绍了“怎么使用MemFire Cloud构建Angular应用程序”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
我们提供的服务有:网站设计、做网站、微信公众号开发、网站优化、网站认证、山东ssl等。为成百上千家企事业单位解决了网站和推广的问题。提供周到的售前咨询和贴心的售后服务,是有科学管理、有技术的山东网站制作公司
MemFire Cloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专注于编写前端应用程序代码,加速WEB或APP应用开发。
此示例提供了使用 MemFire Cloud 和 angular构建简单用户管理应用程序(从头开始)的步骤。这包括:
MemFire Cloud云数据库:用于存储用户数据的 MemFireDB数据库。
MemFire Cloud用户验证:用户可以使用魔法链接登录(只需要电子邮件,不需要密码)。
MemFire Cloud存储:用户可以上传照片。
行级安全策略:数据受到保护,因此个人只能访问自己的数据。
即时API:创建数据库表时会自动生成 API。
允许用户登录和更新一些基本个人资料详细信息的应用程序:
目的:我们的应用就是通过在这里创建的应用来获得数据库、云存储等一系列资源,并获得该应用专属的API访问链接和访问密钥,用户可以轻松的与以上资源进行交互。
登录cloud.memfiredb.com/auth/login 创建应用
点击应用,视图化创建数据表
1、创建profiles表;
在数据表页面,点击“新建数据表”,页面配置如下:
其中profiles表字段id和auth.users表中的id字段(uuid类型)外键关联。
2、开启Profiles的RLS数据安全访问规则;
选中创建的Profiles表,点击表权限栏,如下图所示,点击"启用RLS"按钮
3、允许每个用户可以查看公共的个人信息资料;
点击"新规则"按钮,在弹出弹框中,选择"为所有用户启用访问权限",输入策略名称,选择"SELECT(查询)"操作,点击“创建策略”按钮,如下图。
4、仅允许用户增删改查本人的个人资料信息;
点击"新规则"按钮,在弹出弹框中,选择"根据用户ID为用户启用访问权限",输入策略名称,选择"ALL(所有)"操作,点击“创建策略”按钮,如下图。
创建云存储的存储桶,用来存储用户的头像图片,涉及操作包括:
1、创建一个存储桶avatars
在该应用的云存储导航栏,点击“新建Bucket”按钮,创建存储桶avatars。
2、允许每个用户可以查看存储桶avatars
选中存储桶avatars,切换到权限设置栏,点击“新规则”按钮,弹出策略编辑弹框,选择“自定义”,如下图所示:
选择SELECT操作,输入策略名称,点击“生成策略”按钮,如下图所示。
3、允许用户上传存储桶avatars;
选中存储桶avatars,切换到权限设置栏,点击“新规则”按钮,弹出策略编辑弹框,选择“自定义”,如下图所示:
选择INSERT操作,输入策略名称,点击“生成策略”按钮,如下图所示。
查看结果
所有数据表及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设置中获取URL和anon的密钥。
在应用->概括页面,获取服务地址以及token信息。
Anon(公开)密钥是客户端API密钥。它允许“匿名访问”您的数据库,直到用户登录。登录后,密钥将切换到用户自己的登录令牌。这将为数据启用行级安全性。
注意:service_role(秘密)密钥可以绕过任何安全策略完全访问您的数据。这个密钥必须保密,并且要在服务器环境中使用,绝不能在客户端或浏览器上使用。 在后续示例代码中,需要提供supabaseUrl和supabaseKey。
当用户点击邮件内魔法链接进行登录时,是需要跳转到我们应用的登录界面的。这里需要在认证设置中进行相关URL重定向的配置。
因为我们最终的应用会在本地的4200端口启动(亦或者其他端口),所以这里我们暂时将url设置为 http://localhost:4200
除此之外,在此界面也可以自定义使用我们自己的smtp服务器。
让我们从头开始构建 Angular应用程序。
我们可以使用Angular CLI来初始化一个名为memfiredb-angular
:
Angular 需要 Node.js (>=14.15 <=16.10) 。
npm install -g @angular/cli npx ng new memfiredb-angular --routing false --style css cd memfiredb-angular
然后让我们安装唯一的附加依赖项:supabase-js
npm install @supabase/supabase-js
最后,我们要将环境变量保存在environment.ts, 我们需要的是 API URL 和您上面anon
复制的密钥。
src/environments/environment.ts文件
export const environment = { production: false, supabaseUrl: "YOUR_SUPABASE_URL", supabaseKey: "YOUR_SUPABASE_KEY" };
现在我们已经有了 API 凭证,通过ng g s supabase
创建一个SupabaseService来初始化 Supabase 客户端并实现与 Supabase API 通信的函数。
src/app/supabase.service.ts
import { Injectable } from '@angular/core'; import {AuthChangeEvent, createClient, Session, SupabaseClient} from '@supabase/supabase-js'; import {environment} from "../environments/environment"; export interface Profile { username: string; website: string; avatar_url: string; } @Injectable({ providedIn: 'root' }) export class SupabaseService { private supabase: SupabaseClient; constructor() { this.supabase = createClient(environment.supabaseUrl, environment.supabaseKey); } get user() { return this.supabase.auth.user(); } get session() { return this.supabase.auth.session(); } get profile() { return this.supabase .from('profiles') .select(`username, website, avatar_url`) .eq('id', this.user?.id) .single(); } authChanges(callback: (event: AuthChangeEvent, session: Session | null) => void) { return this.supabase.auth.onAuthStateChange(callback); } signIn(email: string) { return this.supabase.auth.signIn({email}); } signOut() { return this.supabase.auth.signOut(); } updateProfile(profile: Profile) { const update = { ...profile, id: this.user?.id, updated_at: new Date() } return this.supabase.from('profiles').upsert(update, { returning: 'minimal', // Don't return the value after inserting }); } downLoadImage(path: string) { return this.supabase.storage.from('avatars').download(path); } uploadAvatar(filePath: string, file: File) { return this.supabase.storage .from('avatars') .upload(filePath, file); } }
可以看到界面实在是不怎么优雅,更新下样式,让它好看一些。 修改src/styles.css
文件。
html, body { --custom-font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; --custom-bg-color: #101010; --custom-panel-color: #222; --custom-box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.8); --custom-color: #fff; --custom-color-brand: #24b47e; --custom-color-secondary: #666; --custom-border: 1px solid #333; --custom-border-radius: 5px; --custom-spacing: 5px; padding: 0; margin: 0; font-family: var(--custom-font-family); background-color: var(--custom-bg-color); } * { color: var(--custom-color); font-family: var(--custom-font-family); box-sizing: border-box; } html, body, #__next { height: 100vh; width: 100vw; overflow-x: hidden; } /* Grid */ .container { width: 90%; margin-left: auto; margin-right: auto; } .row { position: relative; width: 100%; } .row [class^='col'] { float: left; margin: 0.5rem 2%; min-height: 0.125rem; } .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12 { width: 96%; } .col-1-sm { width: 4.33%; } .col-2-sm { width: 12.66%; } .col-3-sm { width: 21%; } .col-4-sm { width: 29.33%; } .col-5-sm { width: 37.66%; } .col-6-sm { width: 46%; } .col-7-sm { width: 54.33%; } .col-8-sm { width: 62.66%; } .col-9-sm { width: 71%; } .col-10-sm { width: 79.33%; } .col-11-sm { width: 87.66%; } .col-12-sm { width: 96%; } .row::after { content: ''; display: table; clear: both; } .hidden-sm { display: none; } @media only screen and (min-width: 33.75em) { /* 540px */ .container { width: 80%; } } @media only screen and (min-width: 45em) { /* 720px */ .col-1 { width: 4.33%; } .col-2 { width: 12.66%; } .col-3 { width: 21%; } .col-4 { width: 29.33%; } .col-5 { width: 37.66%; } .col-6 { width: 46%; } .col-7 { width: 54.33%; } .col-8 { width: 62.66%; } .col-9 { width: 71%; } .col-10 { width: 79.33%; } .col-11 { width: 87.66%; } .col-12 { width: 96%; } .hidden-sm { display: block; } } @media only screen and (min-width: 60em) { /* 960px */ .container { width: 75%; max-width: 60rem; } } /* Forms */ label { display: block; margin: 5px 0; color: var(--custom-color-secondary); font-size: 0.8rem; text-transform: uppercase; } input { width: 100%; border-radius: 5px; border: var(--custom-border); padding: 8px; font-size: 0.9rem; background-color: var(--custom-bg-color); color: var(--custom-color); } input[disabled] { color: var(--custom-color-secondary); } /* Utils */ .block { display: block; width: 100%; } .inline-block { display: inline-block; width: 100%; } .flex { display: flex; } .flex.column { flex-direction: column; } .flex.row { flex-direction: row; } .flex.flex-1 { flex: 1 1 0; } .flex-end { justify-content: flex-end; } .flex-center { justify-content: center; } .items-center { align-items: center; } .text-sm { font-size: 0.8rem; font-weight: 300; } .text-right { text-align: right; } .font-light { font-weight: 300; } .opacity-half { opacity: 50%; } /* Button */ button, .button { color: var(--custom-color); border: var(--custom-border); background-color: var(--custom-bg-color); display: inline-block; text-align: center; border-radius: var(--custom-border-radius); padding: 0.5rem 1rem; cursor: pointer; text-align: center; font-size: 0.9rem; text-transform: uppercase; } button.primary, .button.primary { background-color: var(--custom-color-brand); border: 1px solid var(--custom-color-brand); } /* Widgets */ .card { width: 100%; display: block; border: var(--custom-border); border-radius: var(--custom-border-radius); padding: var(--custom-spacing); } .avatar { border-radius: var(--custom-border-radius); overflow: hidden; max-width: 100%; } .avatar.image { object-fit: cover; } .avatar.no-image { background-color: #333; border: 1px solid rgb(200, 200, 200); border-radius: 5px; } .footer { position: absolute; max-width: 100%; bottom: 0; left: 0; right: 0; display: flex; flex-flow: row; border-top: var(--custom-border); background-color: var(--custom-bg-color); } .footer div { padding: var(--custom-spacing); display: flex; align-items: center; width: 100%; } .footer div > img { height: 20px; margin-left: 10px; } .footer > div:first-child { display: none; } .footer > div:nth-child(2) { justify-content: left; } @media only screen and (min-width: 60em) { /* 960px */ .footer > div:first-child { display: flex; } .footer > div:nth-child(2) { justify-content: center; } } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .mainHeader { width: 100%; font-size: 1.3rem; margin-bottom: 20px; } .avatarPlaceholder { border: var(--custom-border); border-radius: var(--custom-border-radius); width: 35px; height: 35px; background-color: rgba(255, 255, 255, 0.2); display: flex; align-items: center; justify-content: center; } /* Auth */ .auth-widget { display: flex; flex-direction: column; gap: 20px; } .auth-widget > .button { display: flex; align-items: center; justify-content: center; border: none; background-color: #444444; text-transform: none !important; transition: all 0.2s ease; } .auth-widget .button:hover { background-color: #2a2a2a; } .auth-widget .button > .loader { width: 17px; animation: spin 1s linear infinite; filter: invert(1); } /* Account */ .account { display: flex; flex-direction: column; gap: 20px; } .account > * > .avatarField { display: flex; align-items: center; margin-bottom: 30px; } .account > * > .avatarField > .avatarContainer { margin-right: 20px; } /* Profile Card */ .profileCard { border-radius: 5px; display: flex; border: var(--custom-border); background-color: var(--custom-panel-color); padding: 20px 20px; margin-bottom: 20px; } .profileCard:last-child { margin-bottom: 0px; } .profileCard > .userInfo { margin-left: 20px; font-weight: 300; display: flex; flex-direction: column; justify-content: center; } .profileCard > .userInfo > p { margin: 0; } .profileCard > .userInfo > .username { font-size: 1.3rem; font-weight: 500; margin-bottom: 5px; } .profileCard > .userInfo > .website { font-size: 0.9rem; color: var(--custom-color-brand); margin-bottom: 10px; text-decoration: none; }
让我们设置一个 Angular 组件来管理登录和注册。我们将使用 Magic Links,因此用户无需使用密码即可使用电子邮件登录。使用Angular CLI 命令创建一个AuthComponent 。ng g c auth
src/app/auth/auth.component.ts
import { Component } from '@angular/core'; import {SupabaseService} from "../supabase.service"; @Component({ selector: 'app-auth', template: ``, }) export class AuthComponent { loading = false; constructor(private readonly supabase: SupabaseService) { } async handleLogin(input: string) { try { this.loading = true; await this.supabase.signIn(input); alert('请检查您的电子邮件以获取登录链接!'); } catch (error:any) { alert(error.error_description || error.message) } finally { this.loading = false; } } }
用户登录后,我们可以允许他们编辑他们的个人资料详细信息并管理他们的帐户。使用Angular CLI 命令创建一个AccountComponent 。ng g c account
src/app/account/account.component.ts
import {Component, Input, OnInit} from '@angular/core'; import {Profile, SupabaseService} from "../supabase.service"; import {Session} from "@supabase/supabase-js"; @Component({ selector: 'app-account', template: `` }) export class AccountComponent implements OnInit { loading = false; profile: Profile | undefined; @Input() session: Session | undefined; constructor(private readonly supabase: SupabaseService) { } ngOnInit() { this.getProfile(); } async getProfile() { try { this.loading = true; let {data: profile, error, status} = await this.supabase.profile; if (error && status !== 406) { throw error; } if (profile) { this.profile = profile; } } catch (error:any) { alert(error.message) } finally { this.loading = false; } } async updateProfile(username: string, website: string, avatar_url: string = '') { try { this.loading = true; await this.supabase.updateProfile({username, website, avatar_url}); } catch (error:any) { alert(error.message); } finally { this.loading = false; } } async signOut() { await this.supabase.signOut(); } }
现在我们已经准备好了所有组件,让我们更新AppComponent:
src/app/app.component.ts
import {Component, OnInit} from '@angular/core'; import {SupabaseService} from "./supabase.service"; @Component({ selector: 'app-root', template: `` }) export class AppComponent implements OnInit { session = this.supabase.session; constructor(private readonly supabase: SupabaseService) { } ngOnInit() { this.supabase.authChanges((_, session) => this.session = session); } }
完成后,在终端窗口中运行它:
npm run start
然后打开浏览器到 http://localhost:4200,你应该会看到完整的应用程序。
每个 MemFire Cloud项目都配置了存储,用于管理照片和视频等大文件。
让我们为用户创建一个头像,以便他们可以上传个人资料照片。使用Angular CLI 命令创建AvatarComponent 。ng g c avatar
src/app/avatar/avatar.component.ts
import {Component, EventEmitter, Input, Output} from '@angular/core'; import {SupabaseService} from "../supabase.service"; import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser"; @Component({ selector: 'app-avatar', template: ``, }) export class AvatarComponent { _avatarUrl: SafeResourceUrl | undefined; uploading = false; @Input() set avatarUrl(url: string | undefined) { if (url) { this.downloadImage(url); } }; @Output() upload = new EventEmitter(); constructor( private readonly supabase: SupabaseService, private readonly dom: DomSanitizer ) { } async downloadImage(path: string) { try { const {data} = await this.supabase.downLoadImage(path); if (data instanceof Blob) { this._avatarUrl = this.dom.bypassSecurityTrustResourceUrl( URL.createObjectURL(data) ); } } catch (error:any) { console.error('下载图片出错: ', error.message); } } async uploadAvatar(event: any) { try { this.uploading = true; if (!event.target.files || event.target.files.length === 0) { throw new Error('必须选择要上载的图像。'); } const file = event.target.files[0]; const fileExt = file.name.split('.').pop(); const fileName = `${Math.random()}.${fileExt}`; const filePath = `${fileName}`; await this.supabase.uploadAvatar(filePath, file); this.upload.emit(filePath); this.downloadImage(filePath) } catch (error:any) { alert(error.message); } finally { this.uploading = false; } } }
然后我们可以在AccountComponenthtml 模板的顶部添加小部件:
src/app/account/account.component.ts
import {Component, Input, OnInit} from '@angular/core'; import {Profile, SupabaseService} from "../supabase.service"; import {Session} from "@supabase/supabase-js"; @Component({ selector: 'app-account', template: ` ` }) export class AccountComponent implements OnInit { loading = false; profile: Profile | undefined; @Input() session: Session | undefined; constructor(private readonly supabase: SupabaseService) { } ngOnInit() { this.getProfile(); } async getProfile() { try { this.loading = true; let {data: profile, error, status} = await this.supabase.profile; if (error && status !== 406) { throw error; } if (profile) { this.profile = profile; } } catch (error:any) { alert(error.message) } finally { this.loading = false; } } async updateProfile(username: string, website: string, avatar_url: string = '') { try { this.loading = true; await this.supabase.updateProfile({username, website, avatar_url}); alert("修改成功") } catch (error:any) { alert(error.message); } finally { this.loading = false; } } async signOut() { await this.supabase.signOut(); } }
“怎么使用MemFire Cloud构建Angular应用程序”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!