安装
以最近的 uniapp 开发微信小程序为例。
1 2 npm install @supabase/supabase-js npm install supabase-wechat-stable-v2
配置数据库连接
1 2 3 4 5 6 7 8 import { createClient } from "supabase-wechat-stable-v2" const supabaseUrl = "https://xxxx.baseapi.memfiredb.com" ;const publicAnonKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ;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 => { 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 } 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_userafter 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_userafter 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输入框添加失去焦点时的事件函数 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 ();
这样就可以在使用其自定义链式调用的情况下,正常发送无需等待的异步请求。