Skip to content

操作指南:收藏集 API(用户策划列表)

FT 参考:FT299(NENE2-FT/collectionlog)——用户策划文章收藏集:is_public/private 可见性(非所有者访问私有收藏集时返回 404),UNIQUE(collection_id, article_id) 去重,位置排序,仅所有者可写,20 个测试 / 34 个断言全部通过。

本指南展示如何构建用户策划收藏集 API,让用户可以创建命名列表、向其中添加文章,并控制公开/私有可见性。

数据库结构

sql
CREATE TABLE collections (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id    INTEGER NOT NULL,
    name       TEXT    NOT NULL,
    is_public  INTEGER NOT NULL DEFAULT 0,  -- 0=私有,1=公开
    created_at TEXT    NOT NULL,
    updated_at TEXT    NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE TABLE collection_items (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    collection_id INTEGER NOT NULL,
    article_id    INTEGER NOT NULL,
    position      INTEGER NOT NULL,
    added_at      TEXT    NOT NULL,
    UNIQUE (collection_id, article_id),
    FOREIGN KEY (collection_id) REFERENCES collections(id),
    FOREIGN KEY (article_id)    REFERENCES articles(id)
);

UNIQUE(collection_id, article_id) 防止同一篇文章在收藏集中出现两次。position 支持有序显示。

端点

方法路径认证描述
POST/collectionsX-User-Id创建收藏集
GET/collections/{id}X-User-Id获取收藏集(可见性检查)
PUT/collections/{id}X-User-Id(所有者)更新名称/可见性
DELETE/collections/{id}X-User-Id(所有者)删除收藏集
POST/collections/{id}/itemsX-User-Id(所有者)添加文章
DELETE/collections/{id}/items/{articleId}X-User-Id(所有者)移除文章

可见性——私有收藏集返回 404

php
$isOwner  = (int) $collection['user_id'] === $actorId;
$isPublic = (bool) $collection['is_public'];

if (!$isOwner && !$isPublic) {
    return $this->responseFactory->create(['error' => 'collection not found'], 404);
}

非所有者尝试访问私有收藏集时收到 404,而非 403。这样可以防止泄露私有收藏集的存在。

仅所有者可写

php
if ((int) $collection['user_id'] !== $actorId) {
    return $this->responseFactory->create(['error' => 'access denied'], 403);
}

添加、移除、更新和删除操作需要操作者是收藏集所有者。与可见性不同,写权限失败返回 403(此时收藏集的存在已知晓)。

UNIQUE(collection_id, article_id)——去重

数据库约束防止同一篇文章在收藏集中出现两次。应用在插入前检查重复:

php
// 仓库在 addItem() 之前检查 findItem()
if ($this->repository->findItem($id, $articleId) !== null) {
    return $this->responseFactory->create(['error' => 'article already in collection'], 409);
}
$this->repository->addItem($id, $articleId, date('c'));

is_public 作为布尔整数

php
$isPublic = isset($body['is_public']) && $body['is_public'] === true;

is_public 在 SQLite 中存储为 INTEGER(0/1)。读取时:(bool) $collection['is_public']。写入时:严格的 === true 检查防止字符串 "true" 启用公开访问。

响应结构

php
private function formatCollection(array $collection, array $items): array
{
    return [
        'id'         => (int)    $collection['id'],
        'user_id'    => (int)    $collection['user_id'],
        'name'       => (string) $collection['name'],
        'is_public'  => (bool)   $collection['is_public'],
        'item_count' => count($items),
        'items'      => array_map(fn($item) => [
            'article_id' => (int)    $item['article_id'],
            'title'      => (string) $item['article_title'],
            'position'   => (int)    $item['position'],
            'added_at'   => (string) $item['added_at'],
        ], $items),
        'created_at' => (string) $collection['created_at'],
        'updated_at' => (string) $collection['updated_at'],
    ];
}

反模式

反模式风险
访问私有收藏集返回 403向非所有者泄露收藏集的存在(信息泄露)
允许任意用户向任意收藏集添加条目非所有者向他人收藏集注入内容
UNIQUE(collection_id, article_id)同一篇文章被添加两次;产生令人困惑的重复条目
接受 "true" 字符串作为 is_public类型混淆:松散比较下任何字符串都是真值
无 position 字段条目始终按插入顺序显示;无法重新排序
删除收藏集时无所有权检查任意用户可删除任意收藏集
暴露 item_count 但不包含条目向私有收藏集的非所有者泄露收藏集大小

基于 MIT 许可证发布。