RPC 函数详情
高清配额管理
免费用户每日高清资源访问配额的查询、检查与消耗
概述
高清(HD)漫画资源默认仅对 VIP 用户开放。为了让免费用户也能体验高清内容,系统提供每日有限的免费高清配额。三个 RPC 函数配合管理这一机制:
| 函数 | 职责 | 调用者 |
|---|---|---|
get_premium_quota | 统一入口:VIP 返回无限额度,免费用户返回真实配额 | 客户端(通过 JWT 自动识别用户) |
check_free_user_premium_quota | 查询免费用户的今日配额详情 | 被 get_premium_quota 内部调用,也可直接调用 |
record_free_premium_view | 消耗一次配额并记录浏览 | 客户端在免费用户访问高清章节时调用 |
三个函数均为 SECURITY DEFINER。每日限额从 app_settings 表的 FREE_DAILY_LIMIT 键读取,默认兜底值为 10。
涉及的表
| 表 | 用途 |
|---|---|
free_user_daily_premium_views | 记录免费用户每日高清浏览,(user_id, chapter_id, view_date) 联合唯一 |
app_settings | 存储 FREE_DAILY_LIMIT 配置值 |
profiles | 查询 vip_expiration_date 判断 VIP 状态 |
get_premium_quota
统一的配额查询入口。通过 auth.uid() 自动识别当前用户,VIP 用户直接返回无限额度,免费用户内部调用 check_free_user_premium_quota 获取真实配额。
参数
无参数。通过 JWT 中的 auth.uid() 自动获取用户身份。
未登录时(auth.uid() = NULL)函数直接返回空结果集。
返回字段
| 字段 | 类型 | 说明 |
|---|---|---|
usedToday | integer | 今日已使用次数(VIP 固定为 0) |
dailyLimit | integer | 每日上限(VIP 为 2147483647 即 Max Int) |
remaining | integer | 剩余次数(VIP 为 2147483647) |
isVip | boolean | 是否为 VIP 用户 |
调用示例
val quota = supabase.postgrest
.rpc("get_premium_quota")
.decodeSingle<PremiumQuota>()
if (quota.isVip || quota.remaining > 0) {
// 允许访问高清资源
}const { data } = await supabase.rpc('get_premium_quota')
// data: { usedToday, dailyLimit, remaining, isVip }内部逻辑
auth.uid()
│
├── NULL → 返回空
│
├── VIP(vip_expiration_date >= now())
│ → { usedToday: 0, dailyLimit: MaxInt, remaining: MaxInt, isVip: true }
│
└── 免费用户
→ 调用 check_free_user_premium_quota(uid)
→ { usedToday, dailyLimit, remaining, isVip: false }check_free_user_premium_quota
查询免费用户的今日高清配额详情。通常由 get_premium_quota 内部调用,也可直接调用以跳过 VIP 判断。
参数
Prop
Type
返回字段
| 字段 | 类型 | 说明 |
|---|---|---|
used_today | integer | 今日已使用次数 |
remaining | integer | 剩余次数(≥ 0) |
can_access | boolean | 是否还能访问(used < limit) |
daily_limit | integer | 每日上限(来自 app_settings) |
调用示例
val quota = supabase.postgrest
.rpc("check_free_user_premium_quota") {
parameter("p_user_id", userId)
}
.decodeSingle<FreeQuotaStatus>()const { data } = await supabase
.rpc('check_free_user_premium_quota', {
p_user_id: userId,
})record_free_premium_view
消耗一次免费高清配额。在免费用户打开高清章节时调用,函数内部处理去重(同一章节当日重复访问不消耗配额)和超额检查。
参数
Prop
Type
返回字段
| 字段 | 类型 | 说明 |
|---|---|---|
success | boolean | 是否允许访问 |
used_today | integer | 今日已使用次数(含本次) |
remaining | integer | 剩余次数 |
daily_limit | integer | 每日上限 |
status | text | 状态码,见下表 |
status 值
| 值 | 含义 | success |
|---|---|---|
success | 记录成功,配额 -1 | true |
already_viewed_today | 今日已看过该章节,不重复扣除 | true |
quota_exceeded | 今日配额已用完 | false |
调用示例
val result = supabase.postgrest
.rpc("record_free_premium_view") {
parameter("p_user_id", userId)
parameter("p_chapter_id", chapterId)
}
.decodeSingle<PremiumViewResult>()
when (result.status) {
"success" -> // 正常消耗一次配额,加载高清
"already_viewed_today" -> // 今日已看过,免费放行
"quota_exceeded" -> // 提示用户升级 VIP
}const { data } = await supabase
.rpc('record_free_premium_view', {
p_user_id: userId,
p_chapter_id: chapterId,
})
if (!data.success) {
// 配额已用完,引导升级
}执行流程
record_free_premium_view(user, chapter)
│
├── 今天已看过该章节?
│ → 是:返回 { success: true, status: "already_viewed_today" }
│
├── 今日用量 ≥ 每日上限?
│ → 是:返回 { success: false, status: "quota_exceeded" }
│
└── 插入 free_user_daily_premium_views(ON CONFLICT DO NOTHING)
→ 返回 { success: true, status: "success" }同一章节当日重复访问返回 already_viewed_today(success = true),不消耗额外配额。这意味着用户可以反复阅读已解锁的章节。