认证

发送短信钩子

使用自定义短信提供商发送认证消息


在消息发送前执行。该钩子可用于:

  • 使用区域性短信服务提供商
  • 使用替代消息渠道(如 WhatsApp)
  • 调整消息内容以包含平台特定字段,例如 AppHash

(注:保持所有技术术语和链接不变,仅翻译描述性内容。AppHash作为专有名词保留,其链接和格式标记也保持原样。)

输入参数

字段类型描述
userUser尝试登录的用户对象
smsobject短信发送流程的元数据,包含一次性密码(OTP)
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
{ "user": { "id": "6481a5c1-3d37-4a56-9f6a-bee08c554965", "aud": "authenticated", "role": "authenticated", "email": "", "phone": "+1333363128", "phone_confirmed_at": "2024-05-13T11:52:48.157306Z", "confirmation_sent_at": "2024-05-14T12:31:52.824573Z", "confirmed_at": "2024-05-13T11:52:48.157306Z", "phone_change_sent_at": "2024-05-13T11:47:02.183064Z", "last_sign_in_at": "2024-05-13T11:52:48.162518Z", "app_metadata": { "provider": "phone", "providers": ["phone"] }, "user_metadata": {}, "identities": [ { "identity_id": "3be5e552-65aa-41d9-9db9-2a502f845459", "id": "6481a5c1-3d37-4a56-9f6a-bee08c554965", "user_id": "6481a5c1-3d37-4a56-9f6a-bee08c554965", "identity_data": { "email_verified": false, "phone": "+1612341244428", "phone_verified": true, "sub": "6481a5c1-3d37-4a56-9f6a-bee08c554965" }, "provider": "phone", "last_sign_in_at": "2024-05-13T11:52:48.155562Z", "created_at": "2024-05-13T11:52:48.155599Z", "updated_at": "2024-05-13T11:52:48.159391Z" } ], "created_at": "2024-05-13T11:45:33.7738Z", "updated_at": "2024-05-14T12:31:52.82475Z", "is_anonymous": false }, "sms": { "otp": "561166" }}

输出

  • 不需要任何输出。返回状态码200的空响应即表示成功响应。

您的公司使用工作进程来管理所有与消息相关的任务。出于性能考虑,消息系统通过作业队列按间隔发送消息。消息不会立即发送,而是通过pg_cron定期批量发送。

创建存储作业的表

1
2
3
4
5
6
7
8
9
10
create table job_queue ( job_id uuid primary key default gen_random_uuid(), job_data jsonb not null, created_at timestamp default now(), status text default 'pending', priority int default 0, retry_count int default 0, max_retries int default 2, scheduled_at timestamp default now());

创建钩子函数:

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
create or replace function send_sms(event jsonb) returns void as $$declare job_data jsonb; scheduled_time timestamp; priority int;begin -- 从事件json中提取手机号和验证码 job_data := jsonb_build_object( 'phone', event->'user'->>'phone', 'otp', event->'sms'->>'otp' ); -- 计算最近的5分钟时间窗口作为计划时间 scheduled_time := date_trunc('minute', now()) + interval '5 minute' * floor(extract('epoch' from (now() - date_trunc('minute', now())) / 60) / 5); -- 动态分配优先级(示例逻辑:计划时间越早优先级越高) priority := extract('epoch' from (scheduled_time - now()))::int; -- 将作业插入job_queue表 insert into job_queue (job_data, priority, scheduled_at, max_retries) values (job_data, priority, scheduled_time, 2);end;$$ language plpgsql;grant all on table public.job_queue to supabase_auth_admin;revoke all on table public.job_queue from authenticated, anon;

创建定期运行并出队作业的函数

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
create or replace function dequeue_and_run_jobs() returns void as $$declare job record;begin for job in select * from job_queue where status = 'pending' and scheduled_at <= now() order by priority desc, created_at for update skip locked loop begin -- 在此处添加作业处理逻辑 -- 为演示目的,我们仅将作业状态更新为'completed' update job_queue set status = 'completed' where job_id = job.job_id; exception when others then -- 处理作业失败和重试逻辑 if job.retry_count < job.max_retries then update job_queue set retry_count = retry_count + 1, scheduled_at = now() + interval '1 minute' -- 延迟1分钟重试 where job_id = job.job_id; else update job_queue set status = 'failed' where job_id = job.job_id; end if; end; end loop;end;$$ language plpgsql;grant execute on function public.dequeue_and_run_jobs to supabase_auth_admin;revoke execute on function public.dequeue_and_run_jobs from authenticated, anon;

配置pg_cron按间隔运行作业。您可以使用crontab.guru等工具检查作业是否按预期计划运行。确保在Database > Extensions中启用了pg_cron

1
2
3
4
5
select cron.schedule( '* * * * *', -- 这个cron表达式表示每分钟运行一次 'select dequeue_and_run_jobs();' );