Files
ulthon_information/.sisyphus/plans/category-api.md
augushong 0e8944bc7f docs: 为 category-api 和 apikey-article-api 添加项目笔记文件
- 添加 category-api 的 problems、decisions、learnings、issues 文档
- 添加 apikey-article-api 的 issues、decisions、learnings 文档
- 包含架构决策、问题记录和学习总结
2026-04-28 21:03:15 +08:00

20 KiB
Raw Blame History

分类查询 API 接口

TL;DR

简要描述: 在 API 模块新增只读分类查询接口(列表 + 详情),支持扁平列表和树形结构两种输出格式,使用 ApiKeyAuth 认证。

交付物:

  • app/api/controller/Categories.php — 分类查询控制器index + read 两个方法)
  • AGENTS.md — 更新项目知识库API 控制器数量和描述)

预估工作量: Quick单文件 + 文档更新) 并行执行: YES — Task 1 和 Task 2 可并行 关键路径: Task 1 + Task 2 → F1-F4


Context

原始需求

用户需要在 API 模块中增加分类Category查询接口当前 API 模块缺少分类相关的查询能力。

访谈摘要

关键讨论:

  • 操作范围: 仅查询index + read不需要增删改
  • 认证方式: 需要 ApiKeyAuth 中间件认证
  • 数据格式: 同时支持扁平列表(默认)和树形结构(通过 tree=1 参数切换)

已有参考:

  • app/api/controller/Articles.php — RESTful API 控制器参考
  • app/api/controller/Attachments.php — API 控制器模式参考($this->request 风格)
  • app/model/Category.php — Category 模型(含树形结构方法)

Metis 审查

已识别并处理的缺口:

  • Category 模型没有 SoftDelete trait → 必须手动过滤 where('delete_time', 0)
  • getListLevel() 不过滤 status → 树形模式需要额外过滤
  • getListLevel() 有静态缓存问题 → 不直接使用,改用自建查询 + array2level()
  • type 字段在数据库中是 string 类型 → 注意类型比较

Work Objectives

核心目标

为 API 模块添加只读分类查询接口,遵循现有 API 控制器模式ApiKeyAuth + json_message支持扁平列表和树形结构输出。

具体交付物

  • app/api/controller/Categories.php — 包含 index()read($id) 两个公开方法
  • AGENTS.md — 更新 STRUCTURE 和 CODE MAP 中的 API 控制器信息

完成标准

  • 未认证请求返回 HTTP 401
  • 扁平列表分页正常,返回 {code: 0, msg: '', data: {list, total, page}}
  • tree=1 返回树形嵌套结构
  • type/status/pid 过滤参数正常工作
  • 单条查询返回分类详情(含父级和兄弟分类信息)
  • 不存在的分类 ID 返回错误提示
  • 已软删除的分类不出现在结果中

Must Have

  • ApiKeyAuth 中间件认证
  • 手动过滤 delete_time = 0Category 模型无 SoftDelete trait
  • 默认只返回 status = 1 的分类(除非显式传入 status 参数)
  • limit 参数限制在 1-100 范围内
  • 遵循现有 API 控制器代码风格

Must NOT Have护栏

  • 不得修改 Category 模型、getListLevel()array2level() 或任何现有文件
  • 不得添加 save/update/delete 等写入接口
  • 不得添加缓存层(模型已有 AutoClearCache
  • 不得添加关键词搜索功能
  • 不得加载 posts 关联数据(仅返回分类本身信息)
  • 不得使用 declare(strict_types=1)(遵循 api 模块多数模式)

Verification Strategy

零人工干预 — 全部验证由 agent 执行。不接受需要"用户手动测试"的验收标准。

测试决策

  • 基础设施存在: 否(项目无 PHPUnit 配置,无 tests/ 目录)
  • 自动化测试: 无
  • 框架: 无

QA 策略

所有任务必须包含 agent 执行的 QA 场景。 证据保存到 .sisyphus/evidence/task-{N}-{scenario-slug}.{ext}

  • API 接口: 使用 Bash (curl) — 发送请求,断言状态码 + 响应字段

Execution Strategy

并行执行波次

Wave 1立即开始 — 2个任务可并行:
├── Task 1: 创建 Categories 控制器 [quick]
└── Task 2: 更新 AGENTS.md 文档 [quick]

Wave FINALWave 1 完成后 — 4个并行审查:
├── Task F1: 计划合规审计 (oracle)
├── Task F2: 代码质量审查 (unspecified-high)
├── Task F3: 实际 QA 测试 (unspecified-high)
└── Task F4: 范围忠实度检查 (deep)
→ 呈现结果 → 获取用户明确确认

关键路径: Task 1 → F1-F4 → 用户确认
并行加速: Task 1 和 Task 2 并行; F1-F4 并行审查
最大并发: 4

依赖矩阵

  • 1: 无依赖 → 被 F1-F4 依赖
  • 2: 无依赖 → 被 F1-F4 依赖(与 Task 1 并行)
  • F1-F4: 依赖 Task 1、Task 2 → 无后续依赖

Agent 调度摘要

  • Wave 1: 2 个任务 — T1 → quick, T2 → quick
  • FINAL: 4 个任务 — F1 → oracle, F2 → unspecified-high, F3 → unspecified-high, F4 → deep

TODOs

  • 1. 创建 Categories API 控制器

    What to do:

    • 创建文件 app/api/controller/Categories.php
    • 继承 app\BaseController
    • 添加 protected $middleware = [ApiKeyAuth::class](导入 use app\middleware\ApiKeyAuth
    • 实现 index() 方法:
      • $this->request 获取参数:page默认1limit默认15范围1-100typestatus默认1pidtree默认0
      • 基础查询条件:Category::where('delete_time', 0)Category 模型无 SoftDelete trait必须手动过滤
      • 如果 status 参数未传,默认添加 ->where('status', 1)
      • 如果传了 type 参数,添加 ->where('type', $type)
      • 如果传了 pid 参数,添加 ->where('pid', $pid)
      • 如果 tree=1
        • 执行上述条件查询,获取所有匹配记录(不分页),按 sort ASC 排序
        • 调用 array2level($list, 0, 1) 构建树形结构(注意 array2level 使用 static $list,调用前需要理解其机制:传入 $level=0 会触发 static 重置逻辑,或者直接用 $pid=0 作为根节点)
        • 返回 json_message(['list' => $treeList])
      • 如果不是 tree 模式:
        • 使用 ->paginate(['page' => $page, 'list_rows' => $limit]) 分页
        • sort ASC 排序
        • 返回 json_message(['list' => $list->items(), 'total' => $list->total(), 'page' => $page])
    • 实现 read($id) 方法:
      • $this->request->param('id') 获取 ID遵循 Articles 控制器模式,使用 $this->request 而非方法参数注入)
      • 验证 ID 有效intval > 0
      • 查询:Category::where('delete_time', 0)->where('id', $id)->find()
      • 附加获取器数据:->append(['model_parent', 'model_siblings'])
      • 未找到返回 json_message('分类不存在')
      • 成功返回 json_message($category)
    • 不添加 declare(strict_types=1)(遵循 api 模块 Attachments/ApiKeyInfo 模式)

    Must NOT do:

    • 不得修改 Category 模型或任何现有文件
    • 不得添加 save/update/delete 方法
    • 不得加载 posts 关联
    • 不得添加缓存逻辑
    • 不得使用 declare(strict_types=1)

    Recommended Agent Profile:

    • Category: quick
      • Reason: 单文件创建约80-120行代码逻辑清晰有完整参考模板
    • Skills: []
      • 无需额外技能,纯 PHP 控制器代码

    Parallelization:

    • Can Run In Parallel: N/A仅此一个任务
    • Parallel Group: Wave 1单独
    • Blocks: F1, F2, F3, F4
    • Blocked By: 无(立即开始)

    References:

    执行者没有访谈上下文。以下是他们唯一的指南。

    Pattern References现有代码模式:

    • app/api/controller/Attachments.php主要参考模板。注意其代码风格:$this->request 取参方式、use 导入风格、无 declare(strict_types=1)、中间件声明方式
    • app/api/controller/Articles.php:56-69read() 方法的错误处理模式ID 验证、模型查询、未找到时返回错误
    • app/api/controller/Articles.php:21-53index() 方法的分页模式:page/limit 取参、paginate() 调用、json_message 返回格式

    Model References模型参考:

    • app/model/Category.php — Category 模型完整代码。关键:字段定义、getListLevel() 静态方法、获取器(getModelParentAttr/getModelSiblingsAttr/getTitleImgAttr/getTplNameAttr
    • app/common.php:199array2level() 全局辅助函数,用于将扁平数组转为树形结构。注意其使用 static $list 累积机制

    Middleware References中间件参考:

    • app/middleware/ApiKeyAuth.php — API 认证中间件。支持 Authorization: Bearer {key}X-API-Key header。成功时注入 admin_id/api_key_id/权限到 Request

    Helper References辅助函数参考:

    • app/common.php:29json_message($data, $code, $msg) 全局函数。传入字符串且非 http 开头 → code=500 错误响应;传入数组 → code=0 成功响应

    Database Schema数据库结构:

    • database/migrations/20200418120827_create_table_category.phpul_category 表结构id, title, pid, level, tpl_name, title_img, desc, status, type(string), sort, create_time, update_time, delete_time

    Why Each Reference Matters:

    • Attachments.php — 提供控制器骨架代码包括类声明、middleware 属性、方法签名风格
    • Articles.php:read() — 提供单条查询的完整错误处理流程,可复制修改
    • Articles.php:index() — 提供分页查询的完整流程,包括参数获取和返回格式
    • Category.php — 提供模型字段名、可用获取器、查询方法
    • array2level() — 树形结构构建依赖此函数,必须理解其 static 变量机制
    • ApiKeyAuth.php — 理解认证如何工作,确保中间件正确生效
    • json_message() — 确保响应格式与现有 API 一致
    • category migration — 确认字段类型type 是 string 不是 int

    Acceptance Criteria:

    仅 Agent 可执行的验证 — 禁止人工操作。

    • 文件存在: app/api/controller/Categories.php
    • PHP 语法正确: php -l app/api/controller/Categories.phpNo syntax errors

    QA Scenarios强制 — 无此内容则任务不完整):

    Scenario: 未认证请求被拒绝
      Tool: Bash (curl)
      Preconditions: 开发服务器运行在 8010 端口
      Steps:
        1. 执行 curl -s -w "\n%{http_code}" http://127.0.0.1:8010/index.php/api/categories
        2. 检查 HTTP 状态码是否为 401
        3. 检查响应 JSON 中 code 字段是否为 401
      Expected Result: HTTP 401, {"code": 401, "msg": "...API Key...", "data": null}
      Failure Indicators: HTTP 200 或 code=0 表示认证未生效
      Evidence: .sisyphus/evidence/task-1-unauthorized.txt
    
    Scenario: 扁平列表查询(正常路径)
      Tool: Bash (curl)
      Preconditions: 有效的 API Key分类表中有数据
      Steps:
        1. 执行 curl -s -H "X-API-Key: <valid_key>" "http://127.0.0.1:8010/index.php/api/categories?page=1&limit=5"
        2. 解析 JSON 响应
        3. 验证 code=0
        4. 验证 data.list 是数组
        5. 验证 data.total 是整数
        6. 验证 data.page 等于 1
        7. 验证 list 中每项包含 id, title, pid, level 字段
        8. 验证 list 中无 delete_time > 0 的项
        9. 验证 list 中所有项 status=1默认行为
      Expected Result: {"code": 0, "msg": "", "data": {"list": [...], "total": N, "page": 1}}
      Failure Indicators: code!=0, 缺少 list/total/page 字段, 出现 delete_time>0 的项
      Evidence: .sisyphus/evidence/task-1-flat-list.txt
    
    Scenario: 按类型过滤
      Tool: Bash (curl)
      Preconditions: 有效 API Key
      Steps:
        1. 执行 curl -s -H "X-API-Key: <valid_key>" "http://127.0.0.1:8010/index.php/api/categories?type=1"
        2. 验证返回的每项 type 字段等于 "1"
      Expected Result: 所有返回项 type="1"
      Failure Indicators: 出现 type!="1" 的项
      Evidence: .sisyphus/evidence/task-1-filter-type.txt
    
    Scenario: 树形结构输出
      Tool: Bash (curl)
      Preconditions: 有效 API Key分类表中有父子层级数据
      Steps:
        1. 执行 curl -s -H "X-API-Key: <valid_key>" "http://127.0.0.1:8010/index.php/api/categories?tree=1&type=3"
        2. 验证 code=0
        3. 验证 data.list 是数组
        4. 验证返回结构包含层级嵌套(有子分类的情况下,子项应嵌套在父项中)
        5. 验证无 delete_time>0 的项
      Expected Result: {"code": 0, "data": {"list": [...嵌套结构...]}}
      Failure Indicators: code!=0, 返回扁平结构而非嵌套
      Evidence: .sisyphus/evidence/task-1-tree.txt
    
    Scenario: 单条分类查询
      Tool: Bash (curl)
      Preconditions: 有效 API Key存在 id=1 的分类
      Steps:
        1. 执行 curl -s -H "X-API-Key: <valid_key>" "http://127.0.0.1:8010/index.php/api/categories/read/id/1"
        2. 验证 code=0
        3. 验证 data 中包含 id=1 的分类信息
        4. 验证包含 model_parent 和 model_siblings 获取器数据
      Expected Result: {"code": 0, "data": {"id": 1, "title": "...", "model_parent": {...}, ...}}
      Failure Indicators: code!=0, 缺少获取器数据
      Evidence: .sisyphus/evidence/task-1-read.txt
    
    Scenario: 查询不存在的分类(错误路径)
      Tool: Bash (curl)
      Preconditions: 有效 API Key
      Steps:
        1. 执行 curl -s -H "X-API-Key: <valid_key>" "http://127.0.0.1:8010/index.php/api/categories/read/id/99999"
        2. 验证 code=500
        3. 验证 msg 包含"不存在"字样
      Expected Result: {"code": 500, "msg": "分类不存在", "data": []}
      Failure Indicators: code=0 或返回空数据而非错误提示
      Evidence: .sisyphus/evidence/task-1-read-notfound.txt
    
    Scenario: limit 参数边界值
      Tool: Bash (curl)
      Preconditions: 有效 API Key
      Steps:
        1. 执行 curl -s -H "X-API-Key: <valid_key>" "http://127.0.0.1:8010/index.php/api/categories?limit=999"
        2. 验证返回数据条数不超过100被 clamp 到最大值)
        3. 执行 curl -s -H "X-API-Key: <valid_key>" "http://127.0.0.1:8010/index.php/api/categories?limit=0"
        4. 验证不会报错0 被 clamp 到最小值 1
      Expected Result: limit=999 返回最多100条; limit=0 不报错
      Failure Indicators: 返回超过100条或报500错误
      Evidence: .sisyphus/evidence/task-1-limit-boundary.txt
    

    Evidence to Capture:

    • task-1-unauthorized.txt — 未认证响应
    • task-1-flat-list.txt — 扁平列表响应
    • task-1-filter-type.txt — type 过滤响应
    • task-1-tree.txt — 树形结构响应
    • task-1-read.txt — 单条查询响应
    • task-1-read-notfound.txt — 不存在分类响应
    • task-1-limit-boundary.txt — limit 边界值响应

    Commit: YES

    • Message: feat(api): 新增分类查询接口(列表+详情,支持树形结构)
    • Files: app/api/controller/Categories.php
    • Pre-commit: php -l app/api/controller/Categories.php
  • 2. 更新 AGENTS.md 项目知识库

    What to do:

    • 更新 AGENTS.md 中与 API 控制器相关的描述,使其反映新增的 Categories 控制器
    • 第18行 STRUCTURE 部分:api/controller/ # API接口(微信/文件/验证码) → 更新为包含分类查询的描述
    • 第59行 CODE MAP 控制器继承链:api\controller\* (直接继承 BaseController) -> 3个控制器 → 更新控制器数量和说明,补充 Categories 控制器信息
    • 保持文件其他内容不变

    Must NOT do:

    • 不得修改 AGENTS.md 中与本次变更无关的内容
    • 不得改动其他章节

    Recommended Agent Profile:

    • Category: quick
      • Reason: 简单的文档文本更新仅需修改2处描述
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1与 Task 1 并行)
    • Blocks: F1-F4
    • Blocked By: 无(立即开始)

    References:

    Pattern References:

    • AGENTS.md:18 — STRUCTURE 部分的 api/controller/ 行,需要更新描述
    • AGENTS.md:59 — CODE MAP 控制器继承链的 api\controller\* 行,需要更新数量

    Why Each Reference Matters:

    • 这两处是唯一需要更新的位置,描述了 API 模块的控制器组成

    Acceptance Criteria:

    • AGENTS.md STRUCTURE 部分提到了分类查询接口
    • AGENTS.md CODE MAP 部分反映了新增的 Categories 控制器

    QA Scenarios强制:

    Scenario: AGENTS.md 内容正确更新
      Tool: Bash (grep)
      Preconditions: Task 1 已完成Categories.php 已创建
      Steps:
        1. 执行 grep -n "api/controller" AGENTS.md 检查 STRUCTURE 部分描述是否包含"分类"相关字样
        2. 执行 grep -n "api.*controller" AGENTS.md 检查 CODE MAP 部分是否反映正确的控制器数量
      Expected Result: 两处描述均已更新,包含 Categories 相关信息
      Failure Indicators: 仍显示旧的"3个控制器"或未提及分类查询
      Evidence: .sisyphus/evidence/task-2-agents-md.txt
    

    Evidence to Capture:

    • task-2-agents-md.txt — 更新后的 AGENTS.md 相关行

    Commit: YES与 Task 1 合并提交)

    • Message: feat(api): 新增分类查询接口(列表+详情,支持树形结构)
    • Files: app/api/controller/Categories.php, AGENTS.md

Final Verification Wave所有实现任务完成后 — 强制执行)

4 个审查 agent 并行运行。全部必须通过。向用户呈现综合结果,获取明确"确认"后方可完成。 不要在获得用户确认前将 F1-F4 标记为完成。

  • F1. 计划合规审计oracle 通读计划。逐条检查 "Must Have"验证实现存在读取文件、curl 接口、运行命令)。逐条检查 "Must NOT Have":搜索代码库中的禁止模式 — 发现则拒绝并给出 file:line。检查 .sisyphus/evidence/ 中的证据文件是否存在。将交付物与计划进行比对。 输出: Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT

  • F2. 代码质量审查unspecified-high 运行 php -l app/api/controller/Categories.php(语法检查)。审查文件:as any、空 catch、console.log、注释掉的代码、未使用的 import。检查 AI 痕迹过度注释、过度抽象、泛化命名data/result/item/temp。验证代码风格与 app/api/controller/Attachments.php 一致。 输出: Syntax [PASS/FAIL] | Style [PASS/FAIL] | Files [N clean/N issues] | VERDICT

  • F3. 实际 QA 测试unspecified-high(用户自行测试) 从干净状态启动开发服务器(php think run -p 8010)。执行 Task 1 中的所有 QA 场景 — 按精确步骤执行,捕获证据。保存到 .sisyphus/evidence/final-qa/。 输出: Scenarios [N/N pass] | Integration [N/N] | Edge Cases [N tested] | VERDICT

  • F4. 范围忠实度检查deep 对比 Task 1 的 "What to do" 与实际 git diff。验证 1:1 — 计划中的内容全部实现(无遗漏),实现的内容全部在计划中(无蔓延)。检查 "Must NOT do" 合规性。检测未说明的变更。 输出: Tasks [N/N compliant] | Unaccounted [CLEAN/N files] | VERDICT


Commit Strategy

  • 1+2: feat(api): 新增分类查询接口(列表+详情,支持树形结构) - app/api/controller/Categories.php, AGENTS.md

Success Criteria

验证命令

# 未认证请求应返回 401
curl -s http://127.0.0.1:8010/index.php/api/categories
# Expected: {"code": 401, "msg": "...", "data": null}

# 认证后扁平列表
curl -s -H "X-API-Key: <valid_key>" "http://127.0.0.1:8010/index.php/api/categories?page=1&limit=5"
# Expected: {"code": 0, "msg": "", "data": {"list": [...], "total": N, "page": 1}}

# 认证后树形结构
curl -s -H "X-API-Key: <valid_key>" "http://127.0.0.1:8010/index.php/api/categories?tree=1&type=1"
# Expected: {"code": 0, "msg": "", "data": {"list": [...嵌套结构...]}}

# 单条分类详情
curl -s -H "X-API-Key: <valid_key>" "http://127.0.0.1:8010/index.php/api/categories/read/id/1"
# Expected: {"code": 0, "msg": "", "data": {"id": 1, "title": "...", ...}}

# 不存在的分类
curl -s -H "X-API-Key: <valid_key>" "http://127.0.0.1:8010/index.php/api/categories/read/id/99999"
# Expected: {"code": 500, "msg": "分类不存在", "data": []}

最终检查清单

  • 所有 "Must Have" 存在
  • 所有 "Must NOT Have" 不存在
  • ApiKeyAuth 认证生效
  • delete_time = 0 过滤生效
  • status 默认过滤为 1
  • 扁平列表分页正常
  • 树形结构嵌套正确
  • type/status/pid 过滤参数工作正常