安装

以最近的 uniapp 开发微信小程序为例。

1
2
npm install @supabase/supabase-js
npm install supabase-wechat-stable-v2

配置数据库连接

1
2
3
4
5
6
7
8
//fireDb.ts
import { createClient } from "supabase-wechat-stable-v2"

//这两个常量可以在MemFireDb官网数据库的api中找到
const supabaseUrl = "https://xxxx.baseapi.memfiredb.com";
const publicAnonKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Create a single supabase client for interacting with your database
export const supabase = createClient(supabaseUrl, publicAnonKey);

微信授权手机号一键登陆

在 uniapp 中,设置 button 的 open-type 为 getPhoneNumber,并监听事件getPhoneNumber。

1
2
3
4
5
6
7
<template>
<button class="btn" open-type="getPhoneNumber" @getphonenumber="wxLogin">
<image
src="data:image/svg+xml;base64,xxx" />
<text>微信快捷登录</text>
</button>
</template>

监听事件发生时,调用 wxLogin 函数,传入一个事件对象,根据 code 值判断是否获取到电话号码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
export const wxLogin = async (e: any) => {
wx.login({
success: async res => {
//在getPhoneNumber事件中,如果code为undefined,则用户未授权,否则用户已经授权,获取到手机号码
if (!e.detail.code) {
wx.showToast({
title: "授权失败",
icon: "none",
duration: 2000
})
return;
}
//微信登录,新用户则自动注册
const { data, error } = await supabase.auth.signInWithWechat({ code: res.code })
let { user: { data: { user } } } = data as any;
setProfile(user);


if (error) {
wx.showToast({
title: JSON.stringify(error) || error?.message,
icon: "none",
duration: 2000
})
} else if (data) {
//获取到用户信息后,绑定电话号码到小程序用户上
const { data, error } = await supabase.auth.wechatBindPhone({
code: e.detail.code,
})
if (error) {
wx.showToast({
title: JSON.stringify(error) || error?.message,
icon: "none",
duration: 2000
})
} else if (data) {
wx.showToast({
title: '登录成功!',
icon: "none",
duration: 1000
})
wx.switchTab({
url: '/pages/index/index'
})
}
}
},
})
}

获取微信头像

操作与获取手机号码类似。

1
2
3
<button class="avatarCoverBtn" open-type="chooseAvatar" @chooseavatar="chooseAvatar">
<image class="avatar" :src="profile.avatar" mode="scaleToFill" />
</button>

在获取到头像的本地路径后,需要转存到对象存储的存储桶中,获取对应的网络地址,然后更新数据库中的头像信息和小程序本地的头像信息。我采用 Pinia 进行状态管理,因此在 updateAvatar 函数中,对 userStore 里的头像信息进行了更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
export const chooseAvatar = (e: any) => {
let { avatarUrl } = e.detail
wx.getImageInfo({
src: avatarUrl, // 图片路径,必须是本地路径,可以相对路径或绝对路径
success: async function (res) {
const file = { fileType: "image", width: res.width, height: res.height, tempFilePath: avatarUrl }
const fileExt = avatarUrl.split('.').pop()
const fileName = `${Math.random()}.${fileExt}`
const filePath = `${profile.value.id}/${fileName}`
let { error: uploadError } = await supabase.storage
.from('avatars')
.upload(filePath, file as any)
if (uploadError) {
throw uploadError
}

//获取具有一年期限的头像url
const { data } = await supabase
.storage
.from('avatars')
.createSignedUrl(filePath, 31556952);
//更新头像
updateAvatar(data!.signedUrl);
}
})
};

注:存储桶我设置为私有,且设置了行级安全策略,因此在存储头像的时候,需要存入对应 uid 文件夹下,否则没有权限。

存储桶的私有和共有,详细描述了二者的区别,以及对应的获取资源 url 的方式。

自己维护users的信息

在 public 中新建 users 表。为其创建两个触发器,分别在 auth.users 插入和更新时触发,用以同步 users 中的变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
CREATE TABLE USERS (
id uuid references auth.users not null primary key,
role text,
email text,
last_sign_in_at text,
raw_app_meta_data jsonb,
raw_user_meta_data jsonb
);
create or replace function public.handle_new_user()
returns trigger as $$
begin
insert into public.users (id, role, email,last_sign_in_at,raw_app_meta_data,raw_user_meta_data)
values (new.id,new.role, new.email,new.last_sign_in_at,new.raw_app_meta_data,new.raw_user_meta_data);
return new;
end;
$$ language plpgsql security definer;

create trigger on_new_user
after insert on auth.users for each row
execute procedure public.handle_new_user ();

create or replace function public.update_user()
returns trigger as $$
begin
UPDATE public.users
SET role = new.role,
email = new.email,
last_sign_in_at = new.last_sign_in_at,
raw_app_meta_data = new.raw_app_meta_data,
raw_user_meta_data = new.raw_user_meta_data
WHERE id = new.id;

return new;
end;
$$ language plpgsql security definer;

create trigger on_update_user
after update on auth.users for each row
execute procedure public.update_user ();

为其设定行级安全策略,仅超级管理员可以访问。

1
(get_my_claim('role'::text) = '"super_admin"'::jsonb)

查询行数

其中 head 表示是否只获取每一行的开头,而不获取具体数据。设置为 true 时,返回的 data 为 null 。estimated 是指在行数小的时候返回具体值,行数多的时候返回估计值。注意不能使用 range ,不然 count 的结果就是 range 的范围了。

1
2
3
4
5
6
7
8
9
10
11
export const getUsersNum = () => {
return new Promise(async (resolve, reject) => {
const { data, count, error } = await supabase
.from('users')
.select('*', { count: 'estimated', head: true })
if (count)
resolve(count);
else
reject();
})
}

input失焦空查询

当为一个input输入框添加失去焦点时的事件函数 blur 时,如果是调用的异步查询函数,比如 supabase 的 select 时,会在查询时添加奇怪的参数,导致查询结果为空数组。

不正常的参数

为了正常查询,在 blur 触发的时候去调用同步函数,通过同步函数去调用异步查询函数即可,就像这样:@blur="()=>getOrders()"

正常查询

可以看见奇怪的事件参数消失了,数据正常查询。

请求未发送

在对数据库进行一个字段更新的异步操作时,发现请求未成功发送。supabase.from('address').update({ 'default': false }).eq('id', *fromAid*);

经过查阅资料得知,supabase 自己封装的链式调用,在不需 await 的情况下,需要用 .then() 来表示请求的构造完成。

1
supabase.from('address').update({ 'default': true }).eq('id', fromAid).then();

这样就可以在使用其自定义链式调用的情况下,正常发送无需等待的异步请求。