Tube - Studio Videos

  • Database - Foreign keys 外鍵
    • 外鍵就是在一張表里,存放另一張表主鍵的字段,用來(lái)建立表與表之間的約束和聯(lián)系
    • 外鍵是數(shù)據(jù)庫(kù)級(jí)別的約束,當(dāng)前例子中,如果用戶注銷,也就是users.id被刪除,那么該用戶下的發(fā)布的視頻都會(huì)被刪除
    • 外鍵與 relations 功能類似,但作用級(jí)別不同
export const videos = pgTable("videos", {
  ...
  userId: uuid("user_id").references(() => users.id, { // 外鍵 - 關(guān)聯(lián)到users表的id字段
    onDelete: "cascade", // 級(jí)聯(lián)刪除,如果用戶被刪除,則刪除該用戶的所有視頻
  }).notNull(),
  categoryId: uuid("category_id").references(() => categories.id, {
    onDelete: "set null", // 級(jí)聯(lián)刪除,如果分類被刪除,則將視頻的分類設(shè)置為null,但不刪除視頻
  }),
  ...
})
  • Database - Relations 關(guān)系
    • 關(guān)于relations與foreign key之間的解釋,請(qǐng)參考 Drizzle soft relations
    • 表與表之間的邏輯聯(lián)系,是業(yè)務(wù)邏輯層面的概念
    • 關(guān)系型數(shù)據(jù)庫(kù)中常見的關(guān)系:One-to-One,One-to-Many,Many-to-Many
    • 視頻與用戶一對(duì)一,視頻對(duì)應(yīng)視頻分類也是一對(duì)一
    export const videoRelations = relations(videos, ({ one }) => ({
      user: one(users, {
        fields: [videos.userId],
        references: [users.id],
      }),
      category: one(categories, {
        fields: [videos.categoryId],
        references: [categories.id],
      }),
    }))
    
    • 用戶與視頻一對(duì)多,或者用戶視頻為空(不傳fields、references)
    export const usersRelations = relations(users, ({ many }) => ({
      videos: many(videos), // 一對(duì)多關(guān)系,用戶可以有多個(gè)視頻,或者為空
    }));
    
  • Cursor-based pagination 游標(biāo)分頁(yè)
    • 我們?cè)讷@取videos數(shù)據(jù)的時(shí)候,考慮到頁(yè)面是無(wú)限加載滾動(dòng)的,因此這個(gè)tRPC路由使用游標(biāo)分頁(yè)
    • 用一個(gè)錨點(diǎn)(例如上一條記錄的id或時(shí)間戳)作為起點(diǎn),往后取數(shù)據(jù);需要記住最后一條記錄來(lái)精準(zhǔn)獲取下一頁(yè)數(shù)據(jù)
    • 輸入?yún)?shù)有2個(gè):cursorlimit
    getAll: protectedProcedure
      .input(
        z.object({
          // 可選游標(biāo)參數(shù),上一次的最后一條數(shù)據(jù),首次請(qǐng)求可不傳
          cursor: z.object({ 
              id: z.uuid(),
              updatedAt: z.date(),
          }).nullish(), 
          // 每頁(yè)數(shù)據(jù)條數(shù)限制
          limit: z.number().min(1).max(100), 
        })
    )
    
    • 使用 prefetchInfinite() 預(yù)取數(shù)據(jù):預(yù)取只加載第一頁(yè)的數(shù)據(jù),因此不用傳 cursor
    void trpc.studio.getAll.prefetchInfinite({
      limit: DEFAULT_LIMIT
    });
    
    • 獲取緩存數(shù)據(jù)時(shí)使用 useSuspenseInfiniteQuery()
    const [videos, query] = trpc.studio.getAll.useSuspenseInfiniteQuery(
      { limit: DEFAULT_LIMIT },
      { getNextPageParam: lastPage => lastPage.nextCursor }
    )
    
    • tRPC路由中的Sql查詢條件:
      • eq(field, value) 等于,field = value
      • and(cond1, cond2, ...) 且,多個(gè)條件同時(shí)滿足,cond1 and cond2 and ...
      • or(cond1, cond2, ...) 或,多個(gè)條件只要一個(gè)滿足,cond1 or cond2 or ...
      • lt(field, value) 小于,Less Than,field < value
      • desc(field) 倒序排序,DESC,根據(jù)field字段倒序排序
    import { eq, and, or, lt, desc } from 'drizzle-orm'
    
    getAll: protectedProcedure
      .input(...)
      .query(async ({ ctx, input }) => {
        const { cursor, limit } = input
        const { id: userId } = ctx.user
    
        const data = await db
          .select()
          .from(videos)
          .where(
            and(
              eq( videos.userId, userId ),  // 只查詢當(dāng)前用戶的視頻
              cursor ? or(
                lt(videos.updatedAt, cursor.updatedAt),  // 查詢更新時(shí)間小于游標(biāo)參數(shù)的記錄
                and(
                  eq(videos.updatedAt, cursor.updatedAt),  // 如果更新時(shí)間相同,則查詢id小于游標(biāo)參數(shù)的記錄
                  lt(videos.id, cursor.id)
                )
              ) : undefined
            )
          )
          .orderBy(desc(videos.updatedAt), desc(videos.id))  // 按更新時(shí)間和ID降序排列,也就是最新的視頻在前面
          .limit(limit + 1)  // 多查詢一條數(shù)據(jù)用于判斷是否還有下一頁(yè)
    
          const hasMore = data.length > limit // 判斷是否還有下一頁(yè)
          const videosData = hasMore ? data.slice(0, -1) : data // 如果還有下一頁(yè),則去掉最后一條數(shù)據(jù)
          const lastVideo = videosData[videosData.length - 1] // 獲取最后一條數(shù)據(jù),用于生成下一頁(yè)的游標(biāo)
          // 生成下一頁(yè)的cursor
          const nextCursor = hasMore ? { 
            id: lastVideo.id,
            updatedAt: lastVideo.updatedAt,
          } : null
    
        return {
          videosData,
          nextCursor, // 是一個(gè)對(duì)象,包含了id和updatedAt兩個(gè)字段
        }
      })
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容