feat(api): GET /v1/public/albums/{token}/ads 公开广告(全局+定向/现签)
ov 公开端点(ads-carousel Task 9,设计 §5.2):相册校验复用 findPublicAlbumOv(不存在/offline/紧急停用统一 404 ALBUM_NOT_PUBLIC); 可见集 = 全局投放(albumIds=null)∨ 绑定本相册,sortOrder 升序、并列 createdAt 升序;imageUrl 逐条现签 GET(TTL=SIGNED_URL_TTL_SECONDS)。 匿名无 guard、不加独立限速档(观众页单次加载一次,与公开相册读同量级, 量级假设 ≤ 百条:全表捞 + 应用层 JSON 过滤)。 public-ov.e2e 追加 ⑧:prismaOv 直种 albums_ov/ads_ov——过滤/排序/现签 (可取回真实字节)/404 同码同 path/空 items/匿名 200;9/9 通过, ads.e2e 16/16 回归通过。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import { parsePublicPhotoLimit } from '../public/public.controller';
|
||||
import { parseCursor } from '../shared/pagination';
|
||||
import { validationFailed } from '../albums/support';
|
||||
import {
|
||||
type PublicAdListResponse,
|
||||
type PublicAlbumResponse,
|
||||
type PublicPhotoDetailResponse,
|
||||
type PublicPhotoListResponse,
|
||||
@@ -79,4 +80,14 @@ export class PublicOvController {
|
||||
): Promise<PublicPhotoDetailResponse> {
|
||||
return this.publicService.getPublicPhoto(token, photoId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 公开广告列表:全局投放 + 绑定本册,现签图 URL(广告位轮播 2026-09-28
|
||||
* 设计 §5.2)。匿名(契约 security: []——观众页单次加载一次,与公开相册读
|
||||
* 同量级,不加独立限速档)。路由与 ':token/photos' 无参数冲突(并列段)。
|
||||
*/
|
||||
@Get(':token/ads')
|
||||
listPublicAds(@Param('token') token: string): Promise<PublicAdListResponse> {
|
||||
return this.publicService.listPublicAds(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ export type PublicPhotoListResponse =
|
||||
components['schemas']['PublicPhotoList'];
|
||||
export type PublicPhotoDetailResponse =
|
||||
components['schemas']['PublicPhotoDetail'];
|
||||
export type PublicAdListResponse =
|
||||
components['schemas']['PublicAdListResponse'];
|
||||
type SignedUrlResponse = components['schemas']['SignedUrl'];
|
||||
|
||||
/** 与 cn public.service 同值(契约 listPublicPhotos limit bounds)。 */
|
||||
@@ -299,6 +301,36 @@ export class PublicOvService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 公开广告列表(广告位轮播 2026-09-28 设计 §5.2):相册校验复用公开相册读
|
||||
* 同一入口(findPublicAlbumOv——不存在/offline/紧急停用统一 404
|
||||
* ALBUM_NOT_PUBLIC);可见集 = 全局投放(albumIds=null)∨ 绑定本相册。
|
||||
* 排序 sortOrder 升序、并列 createdAt 升序(副本 @@index 同序)。imageUrl
|
||||
* 逐条现签 GET(TTL 同公开读 SIGNED_URL_TTL_SECONDS;过期后重拉本端点刷新,
|
||||
* 契约语义——无 expiresAt 字段,PublicAd 只返 URL 裸串)。无广告空 items
|
||||
* (非 404)。量级假设广告 ≤ 百条(设计 §5.2 登记):ads_ov 全表捞 + 应用层
|
||||
* 过滤(JSON 列 includes),与公开相册读同量级——不加独立限速档。
|
||||
*/
|
||||
async listPublicAds(token: string): Promise<PublicAdListResponse> {
|
||||
const album = await this.findPublicAlbumOv(token);
|
||||
const rows = await this.prisma.ads_ov.findMany({
|
||||
orderBy: [{ sortOrder: 'asc' }, { createdAt: 'asc' }],
|
||||
});
|
||||
const visible = rows.filter(
|
||||
(r) =>
|
||||
r.albumIds === null || (r.albumIds as string[]).includes(album.albumId),
|
||||
);
|
||||
const ttl = signedUrlTtlSeconds();
|
||||
const items = await Promise.all(
|
||||
visible.map(async (r) => ({
|
||||
adId: r.adId,
|
||||
imageUrl: await this.storage.presignGet(r.imageObjectKey, ttl),
|
||||
linkUrl: r.linkUrl,
|
||||
})),
|
||||
);
|
||||
return { items };
|
||||
}
|
||||
|
||||
/** hot 排序页:快照清单(TTL 缓存)offset 切片 → 现取行(跳过中途被撤下的)。 */
|
||||
private async listHot(
|
||||
albumId: string,
|
||||
|
||||
Reference in New Issue
Block a user