Logo花火漫画开发文档
函数详情

主页瀑布流

主页信息流聚合接口,一次请求返回轮播图、公告、推荐、最近更新、新上架、人气排行等全部板块数据

概述

home-feed 是客户端主页的核心数据接口。它将多个板块的数据查询并行执行后聚合返回,避免客户端发起多次请求。支持按需选择板块和自定义每个板块的返回数量。

属性
Slughome-feed
方法GET
JWT 校验否(公开接口)
缓存Cache-Control: public, max-age=30, s-maxage=30
CORS允许所有来源

板块说明

接口支持 6 个独立板块,每个板块对应主页 UI 的一个区域:

板块参数名说明数据来源
banners轮播图首页顶部轮播横幅home_banners
announcements公告系统公告和通知announcements
recommended编辑推荐手动标记为推荐的漫画comicsis_recommended = true
recent最近更新按章节更新时间排序get_recent_comics RPC
newlyAdded新上架按漫画创建时间排序comicscreate_at DESC
popular人气排行日/周/月三个维度的排行comicspopularity_* 字段

请求格式

GET /functions/v1/home-feed?include=...&recommendedLimit=...

查询参数

Prop

Type

请求示例

GET /functions/v1/home-feed

不传任何参数,返回全部板块、默认数量。

GET /functions/v1/home-feed?include=banners,recommended,recent&recommendedLimit=6&recentLimit=20

只加载轮播图、推荐和最近更新,自定义数量。

GET /functions/v1/home-feed?include=popular&popularityLimit=20

只加载人气排行,每个维度返回 20 条。

响应格式

响应体为 {"data": {...}} 结构,data 中只包含请求的板块。板块的返回顺序固定为:recommended → recent → newlyAdded → popular → banners → announcements,与 include 参数中的书写顺序无关。

漫画对象(Comic)

recommendedrecentnewlyAddedpopular 板块中每个元素的结构:

Prop

Type

轮播图对象(Banner)

Prop

Type

公告对象(Announcement)

Prop

Type

完整响应示例

{
  "data": {
    "recommended": [
      {
        "documentId": "42",
        "name": "进击的巨人",
        "summary": "那一天,人类终于想起了被支配的恐惧...",
        "cover": "https://bucket.xfmanga.top/res/covers/42.webp",
        "poster": null,
        "popularity": { "daily": 320, "weekly": 1850, "monthly": 6200 },
        "categoryName": "热血",
        "lockStatus": "free",
        "latestChapterTitle": "第139话 向着那棵树",
        "latestChapterUpdatedAt": "2026-03-10T08:00:00Z",
        "latestChapterDocumentId": null,
        "releaseDate": "2009-09-09",
        "isFinished": true
      }
    ],
    "recent": [],
    "newlyAdded": [],
    "popular": {
      "daily": [],
      "weekly": [],
      "monthly": []
    },
    "banners": [
      {
        "id": "b1a2c3d4",
        "title": "新番上线",
        "subtitle": "本周热门新作推荐",
        "imageUrl": "https://bucket.xfmanga.top/img/banners/spring-2026.webp",
        "targetType": "comic",
        "targetValue": "42"
      }
    ],
    "announcements": [
      {
        "id": "a1b2c3d4",
        "title": "系统维护通知",
        "content": "3月20日凌晨2:00-4:00进行系统维护",
        "type": "warning",
        "actionText": "查看详情",
        "actionUrl": "/announcements/a1b2c3d4",
        "isDismissible": true,
        "priority": 10
      }
    ],
    "meta": {
      "generatedAt": "2026-03-15T10:00:00.000Z",
      "limits": {
        "recommended": 12,
        "recent": 12,
        "newlyAdded": 12,
        "popular": 12,
        "banners": 5,
        "announcements": 3
      }
    }
  }
}

核心逻辑

并行查询

所有板块的数据库查询通过 Promise.all 并行执行,互不阻塞。即使客户端请求全部 6 个板块(实际发起约 8 次数据库查询,因为 popular 拆为日/周/月三次),总耗时也接近于单次最慢查询的耗时。

┌─ banners 查询      ─┐
├─ announcements 查询 ─┤
├─ recommended 查询   ─┤
├─ recent RPC 调用    ─┼── Promise.all ── 聚合响应
├─ newlyAdded 查询    ─┤
├─ popular/daily      ─┤
├─ popular/weekly     ─┤
└─ popular/monthly    ─┘

板块过滤(include 参数)

通过 include 参数控制返回哪些板块。未被选中的板块不会执行数据库查询,减少不必要的开销。

为了向后兼容,include=latest 会自动映射为 newlyAddedlatestLimit 参数也会被 newlyAddedLimit 的逻辑接收。

限制数量(Limit Clamping)

所有 limit 参数都经过 clamp 处理:非法值回退到默认值,超出最大值则截断到上限。

板块默认值最大值
recommended1220
recent1230
newlyAdded1230
popular(每个维度)1230
banners510
announcements310

轮播图与公告的时间窗口

home_bannersannouncements 都支持 start_at / end_at 时间窗口。查询时只返回当前时间处于窗口内(或窗口字段为空)的记录:

-- 伪代码,实际通过 Supabase client 构建
WHERE is_active = true
  AND (start_at IS NULL OR start_at <= now())
  AND (end_at IS NULL OR end_at >= now())

这意味着可以提前配置轮播图和公告,设定一个未来的 start_at,到时间后自动生效,无需手动操作。

最近更新(recent)

此板块使用数据库 RPC 函数 get_recent_comics 而非直接查表:

-- get_recent_comics 函数定义
SELECT c.id, c.title, c.summary, c.cover_url, c.poster_url,
       c.lock_status, cat.name as category_name,
       c.latest_chapter_title, c.latest_chapter_updated_at
FROM comics c
LEFT JOIN categories cat ON c.category_id = cat.id
WHERE c.latest_chapter_updated_at IS NOT NULL
ORDER BY c.latest_chapter_updated_at DESC
LIMIT limit_count;

使用 RPC 的原因是需要 JOIN categories 表获取分类名称,并且只返回有章节更新记录的漫画(latest_chapter_updated_at IS NOT NULL)。

人气排行(popular)

人气排行板块按三个时间维度分别查询,响应中以嵌套结构返回:

{
  "popular": {
    "daily":   [/* 按 popularity_daily 降序 */],
    "weekly":  [/* 按 popularity_weekly 降序 */],
    "monthly": [/* 按 popularity_monthly 降序 */]
  }
}

popularity_dailypopularity_weeklypopularity_monthly 字段需要由定时任务配合 RPC 函数计算和更新,home-feed 只负责读取。

数据映射

原始数据库字段通过 mapComic 函数映射为客户端友好的驼峰命名结构。其中 summary 字段会进行 HTML 标签剥离和截断处理(最长 120 字符),避免传输过多数据:

const stripAndTruncateSummary = (value: unknown, maxLength = 120) => {
  // 1. 剥离 HTML 标签
  // 2. 合并多余空白
  // 3. 超过 maxLength 截断并加 "..."
}

关联数据表

home_banners

字段类型说明
iduuid主键
titletext标题
subtitletext副标题
image_urltext横幅图片地址
target_typetext跳转类型,默认 "comic"
target_valuetext跳转目标(漫画 ID 或 URL)
sort_orderinteger排序权重(降序),默认 0
is_activeboolean是否启用,默认 true
start_attimestamptz生效开始时间(null 表示立即生效)
end_attimestamptz生效结束时间(null 表示永不过期)

announcements

字段类型说明
iduuid主键
titletext公告标题
contenttext公告摘要内容
detail_contenttext公告详情正文
announcement_typetext类型(info / warning / error),默认 "info"
action_texttext操作按钮文字
action_urltext操作按钮链接
priorityinteger优先级(降序排列),默认 0
is_activeboolean是否启用,默认 true
is_dismissibleboolean可否关闭,默认 true
is_popupboolean是否弹窗展示,默认 false
start_attimestamptz生效开始时间
end_attimestamptz生效结束时间

comics(主要字段)

字段类型说明
idbigint主键
titletext漫画标题
summarytext简介(可能含 HTML)
cover_urltext封面图地址
poster_urltext海报图地址
lock_statustext锁定状态,默认 "free"
is_recommendedboolean是否编辑推荐
popularity_dailyinteger日人气值
popularity_weeklyinteger周人气值
popularity_monthlyinteger月人气值
category_idbigint关联 categories
latest_chapter_titletext最新章节标题(冗余字段)
latest_chapter_updated_attimestamptz最新章节更新时间(冗余字段)
is_finishedboolean是否完结
release_datedate发布日期
create_at / created_attimestamptz创建时间

latest_chapter_titlelatest_chapter_updated_at 是从 chapters 表冗余到 comics 表的字段,在新章节发布时同步更新,避免主页查询时 JOIN chapters 表。

缓存策略

响应头设置了 Cache-Control: public, max-age=30, s-maxage=30,意味着:

  • CDN 和浏览器都会缓存响应 30 秒
  • 30 秒内的重复请求直接由缓存返回,不会到达 Edge Function
  • 适合主页这种高频访问但数据不需要实时更新的场景

错误响应

{
  "error": {
    "code": "HOME_FEED_ERROR",
    "message": "具体错误信息"
  }
}

任何一个板块查询失败都会导致整个请求返回 500。这是因为 Promise.all 的 fail-fast 特性——如果需要更高的容错性,可以考虑改为 Promise.allSettled 并对失败的板块返回空数组。

客户端调用示例

suspend fun loadHomeFeed(
    sections: List<String> = emptyList(),
): HomeFeedResponse {
    val url = buildString {
        append("${supabaseUrl}/functions/v1/home-feed")
        if (sections.isNotEmpty()) {
            append("?include=${sections.joinToString(",")}")
        }
    }

    val response = httpClient.get(url) {
        header("apikey", supabaseAnonKey)
    }

    return response.body()
}

// 首次进入主页:加载全部板块
val feed = loadHomeFeed()

// 切换到排行榜 Tab:只加载排行
val ranking = loadHomeFeed(listOf("popular"))
async function fetchHomeFeed(
  sections?: string[],
  limits?: Record<string, number>
) {
  const params = new URLSearchParams()

  if (sections?.length) {
    params.set('include', sections.join(','))
  }
  if (limits) {
    for (const [key, value] of Object.entries(limits)) {
      params.set(key, String(value))
    }
  }

  const url = `${SUPABASE_URL}/functions/v1/home-feed?${params}`
  const res = await fetch(url, {
    headers: { apikey: SUPABASE_ANON_KEY },
  })

  if (!res.ok) throw new Error(`Home feed error: ${res.status}`)
  return res.json()
}

// 完整加载
const { data } = await fetchHomeFeed()

// 按需加载 + 自定义数量
const { data } = await fetchHomeFeed(
  ['recommended', 'recent'],
  { recommendedLimit: '6', recentLimit: '20' }
)

On this page