- 添加 category-api 的 problems、decisions、learnings、issues 文档 - 添加 apikey-article-api 的 issues、decisions、learnings 文档 - 包含架构决策、问题记录和学习总结
20 KiB
分类查询 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 = 0(Category 模型无 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 FINAL(Wave 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(默认1)、limit(默认15,范围1-100)、type、status(默认1)、pid、tree(默认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-69—read()方法的错误处理模式:ID 验证、模型查询、未找到时返回错误app/api/controller/Articles.php:21-53—index()方法的分页模式:page/limit取参、paginate()调用、json_message返回格式
Model References(模型参考):
app/model/Category.php— Category 模型完整代码。关键:字段定义、getListLevel()静态方法、获取器(getModelParentAttr/getModelSiblingsAttr/getTitleImgAttr/getTplNameAttr)app/common.php:199—array2level()全局辅助函数,用于将扁平数组转为树形结构。注意其使用static $list累积机制
Middleware References(中间件参考):
app/middleware/ApiKeyAuth.php— API 认证中间件。支持Authorization: Bearer {key}和X-API-Keyheader。成功时注入admin_id/api_key_id/权限到 Request
Helper References(辅助函数参考):
app/common.php:29—json_message($data, $code, $msg)全局函数。传入字符串且非 http 开头 →code=500错误响应;传入数组 →code=0成功响应
Database Schema(数据库结构):
database/migrations/20200418120827_create_table_category.php—ul_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.php→No 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.txtEvidence 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.txtEvidence 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 过滤参数工作正常