- 添加 category-api 的 problems、decisions、learnings、issues 文档 - 添加 apikey-article-api 的 issues、decisions、learnings 文档 - 包含架构决策、问题记录和学习总结
54 KiB
API Key 管理与文章/附件 API
TL;DR
Quick Summary: 为现有 ThinkPHP 6 CMS 添加 API Key 认证机制,后台管理员可管理自己的 API Key,通过 API Key 调用 RESTful 接口完成文章完整 CRUD 和附件完整管理(上传/列表/删除)。
Deliverables:
- 数据库迁移文件:
ul_api_key表- ApiKey 模型
- ApiKeyAuth 中间件(Header 认证)
- 文章 API 控制器(列表/详情/创建/编辑/删除)
- 附件 API 控制器(上传/列表/删除)
- 后台 API Key 管理控制器 + layui 视图
- API 中间件注册文件
Estimated Effort: Medium Parallel Execution: YES - 3 waves Critical Path: 迁移+模型 → 中间件 → API控制器 → 后台管理
Context
Original Request
后台可以管理 API Key,使用 API Key 可以管理文章(列表、发表文章、上传附件等)。
Interview Summary
Key Discussions:
- API Key 粒度:每个管理员一个 API Key
- 权限控制:拥有 Key 即有全部文章管理权限,无需分级
- 过期策略:永不过期,手动禁用/启用
- 文章操作:列表+详情(所有Key); 创建+编辑自己(can_write_own=1); 编辑后台(can_write_other=1); 删除(can_delete控制)
- 附件管理:列表(所有Key); 上传(can_write_own=1); 删除(can_delete控制)
- 数据查询:所有数据不分来源
- 权限模型:三字段(can_write_own/can_write_other/can_delete),每个Key独立控制
- 有删除接口,删除权限由 can_delete 字段控制
- source 字段用于权限判断和数据隔离
- 测试策略:不需要搭建测试基础设施
Research Findings:
- 现有 API 控制器(3个)均无认证,继承 BaseController
- API 路由完全依赖 TP6 自动路由
/api/{controller}/{action} - 不存在
app/api/middleware.php,需新建 - Post 模型使用 SoftDelete + AutoClearCache,关联 categorys/tags/comments
- 文件上传使用
app/UploadFiles.php静态服务类 - 响应格式:
{ code: 0/500, msg: '', data: {} }通过json_message() - 迁移使用
ColumnFormat辅助类,时间戳 int(10)
Metis Review
Identified Gaps (addressed):
- API Key 安全存储:数据库中存储 md5 哈希而非明文,仅在创建时展示一次原文
- 管理员被删除时的 API Key 处理:通过 admin_id 外键关联,管理员删除时 API Key 自动禁用
- 现有 API 控制器不受影响:中间件仅应用于需要认证的路由/控制器
- 防止重复提交:创建文章时生成 uid 防止重复
- 附件类型校验:复用现有
UploadFiles::fileScan()安全扫描
Work Objectives
Core Objective
为 CMS 系统增加 API Key 认证体系,使外部客户端可以通过 API Key 认证后调用 RESTful API 管理文章和附件。
Concrete Deliverables
database/migrations/新增ul_api_key表迁移 + post/upload_files 表加 source 字段迁移app/model/ApiKey.php- API Key 模型app/middleware/ApiKeyAuth.php- API Key 认证中间件app/api/middleware.php- API 应用中间件注册app/api/controller/Articles.php- 文章 API(列表/详情/创建/编辑,无删除)app/api/controller/Attachments.php- 附件管理 API(上传/列表,无删除,数据隔离)app/admin/controller/ApiKey.php- 后台 API Key 管理控制器view/admin/api_key/- 后台 API Key 管理 layui 视图(index.html)
Definition of Done
php think migrate:run成功创建ul_api_key表(含 can_write_own/can_write_other/can_delete 字段)- 后台可以查看/生成/重新生成/禁用/启用 API Key,以及设置三个权限字段
- 使用有效 API Key 可以查询所有文章和附件
- can_write_own=1 时可以创建文章和上传附件,编辑/删除 source='api' 的数据
- can_write_other=1 时可以编辑 source='admin' 的数据
- can_delete=0 时不能删除任何数据
- can_delete=1 时只能删除 source='api' 的数据
- can_delete=2 时可以删除所有数据
- 使用无效/禁用的 API Key 返回 401 错误
Must Have
- API Key 数据库存储 md5 哈希,创建时仅返回一次明文
- 所有 API 响应使用
json_message()格式 - 每个 API Key 有三个独立权限字段:
can_write_own(0/1): 能否创建和编辑 source='api' 的数据can_write_other(0/1): 能否编辑 source='admin' 的数据can_delete(0/1/2): 0=不能删除, 1=只能删API创建的, 2=可删所有
- API 可以查询所有数据(文章列表/详情、附件列表),不区分来源
- 创建文章时自动标记 source='api'
- 上传附件时自动标记 source='api'
- 编辑文章时根据 source 和权限字段判断是否允许
- 删除时根据 can_delete 和 source 判断是否允许
- 文件上传复用现有
UploadFiles::save()及安全扫描 - 后台管理界面沿用现有 layui 风格,可设置三个权限
- 中间件仅保护需要认证的 API 控制器
- post 表和 upload_files 表添加 source 字段(默认 'admin',API 创建时设为 'api')
Must NOT Have (Guardrails)
- 不修改现有控制器或模型(只新增)
- 不改变现有 Session 认证体系
- 不引入 Rate Limiting
- 不引入 API Key 过期时间
- 不修改现有
app/UploadFiles.php文件 - 不引入新前端库
- 不使用 $fillable 白名单
Verification Strategy (MANDATORY)
ZERO HUMAN INTERVENTION - ALL verification is agent-executed. No exceptions.
Test Decision
- Infrastructure exists: NO
- Automated tests: None
- Framework: none
- Verification method: Agent-executed QA via curl commands
QA Policy
Every task MUST include agent-executed QA scenarios.
Evidence saved to .sisyphus/evidence/task-{N}-{scenario-slug}.{ext}.
- API/Backend: Use Bash (curl) - Send requests, assert status + response fields
- Admin UI: Use Playwright (playwright skill) - Navigate, interact, assert DOM, screenshot
Execution Strategy
Parallel Execution Waves
Wave 1 (Start Immediately - 基础设施层):
├── Task 1: 数据库迁移 - ul_api_key 表 [quick]
├── Task 2: ApiKey 模型 [quick]
└── Task 3: ApiKeyAuth 认证中间件 [quick]
Wave 2 (After Wave 1 - API 接口层,MAX PARALLEL):
├── Task 4: 文章 API 控制器 (depends: 1, 2, 3) [unspecified-high]
├── Task 5: 附件 API 控制器 (depends: 1, 2, 3) [unspecified-high]
├── Task 6: 后台 API Key 管理控制器 + 视图 (depends: 1, 2) [unspecified-high]
├── Task 7: API Key 权限查询接口 (depends: 2, 3) [quick]
└── Task 8: 前台 API 文档页面 (depends: 4, 5, 7) [unspecified-high]
Wave FINAL (After ALL tasks — 4 parallel reviews):
├── Task F1: Plan compliance audit (oracle)
├── Task F2: Code quality review (unspecified-high)
├── Task F3: Real manual QA (unspecified-high)
└── Task F4: Scope fidelity check (deep)
-> Present results -> Get explicit user okay
Critical Path: Task 1 → Task 3 → Task 4 → Task 8 → F1-F4 → user okay
Parallel Speedup: ~60% faster than sequential
Max Concurrent: 5 (Wave 2)
Dependency Matrix
| Task | Depends On | Blocks | Wave |
|---|---|---|---|
| 1 | - | 2,3,4,5,6 | 1 |
| 2 | 1 | 3,4,5,6,7 | 1 |
| 3 | 1,2 | 4,5,7 | 1 |
| 4 | 1,2,3 | 8,F1-F4 | 2 |
| 5 | 1,2,3 | 8,F1-F4 | 2 |
| 6 | 1,2 | F1-F4 | 2 |
| 7 | 2,3 | 8 | 2 |
| 8 | 4,5,7 | F1-F4 | 2 |
| F1-F4 | 4,5,6,8 | - | FINAL |
Agent Dispatch Summary
- Wave 1: 3 tasks - T1 →
quick, T2 →quick, T3 →quick - Wave 2: 5 tasks - T4 →
unspecified-high, T5 →unspecified-high, T6 →unspecified-high, T7 →quick, T8 →unspecified-high - FINAL: 4 tasks - F1 →
oracle, F2 →unspecified-high, F3 →unspecified-high, F4 →deep
TODOs
-
1. 数据库迁移 - ul_api_key 表 + post/upload_files 表 source 字段
What to do:
-
创建迁移文件
database/migrations/{timestamp}_create_table_api_key.php -
参照现有迁移结构(如
create_table_admin.php),使用ColumnFormat辅助类 -
表名:
api_key(Phinx 自动加ul_前缀 →ul_api_key) -
字段设计:
id: 自增主键admin_id: int(10) unsigned,关联管理员 ID(NOT NULL)api_key: string(64),存储 md5 哈希值(NOT NULL, UNIQUE)name: stringNormal(100),API Key 名称/备注status: integerTypeStatus(),0=禁用,1=启用,默认1can_write_own: integerTypeStatus(),0=不可, 1=可,默认0(能否创建和编辑API数据)can_write_other: integerTypeStatus(),0=不可, 1=可,默认0(能否编辑后台数据)can_delete: integerTypeStatus(),0=不可删除, 1=仅删API数据, 2=可删所有,默认0create_time: timestamp,创建时间update_time: timestamp,更新时间delete_time: timestamp,软删除时间
-
添加索引:
admin_id(普通索引),api_key(唯一索引) -
仅实现
change()方法(不可逆迁移,遵循项目惯例) -
创建迁移文件
database/migrations/{timestamp}_add_source_to_post_and_upload_files.php -
给
post表添加source字段: stringShort(30),默认值'admin' -
给
upload_files表添加source字段: stringShort(30),默认值'admin' -
source 字段用于标记数据来源:'admin'=后台创建,'api'=API创建
-
仅实现
change()方法
Must NOT do:
- 不添加外键约束(项目无此惯例)
- 不修改现有字段
Recommended Agent Profile:
- Category:
quick- Reason: 单个迁移文件,结构清晰,参照现有模板即可
- Skills: []
- Skills Evaluated but Omitted:
- 无需特殊技能
Parallelization:
- Can Run In Parallel: YES (with nothing - this is the foundation)
- Parallel Group: Wave 1 (with Tasks 2)
- Blocks: Tasks 2, 3, 4, 5, 6
- Blocked By: None (can start immediately)
References:
Pattern References:
database/migrations/20190822043811_create_table_admin.php- Admin 表迁移结构,参照此文件的迁移类结构database/migrations/20200418120809_create_table_post.php- Post 表迁移,参照字段定义方式app/common/ColumnFormat.php- 列定义辅助类,了解stringNormal()/timestamp()/integerTypeStatus()等方法
Why Each Reference Matters:
- Admin 迁移文件展示了本项目的标准迁移写法:类名、命名空间、
change()方法、ColumnFormat 用法 - Post 迁移展示了字段类型选择惯例
- ColumnFormat 是必用的辅助类,必须使用它定义列
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: 迁移文件创建成功并执行 Tool: Bash Preconditions: 项目根目录,数据库配置正确 Steps: 1. 运行 `php think migrate:run` 2. 检查输出无错误 3. 使用 SQLite 命令检查 ul_api_key 表结构 4. 验证字段存在: admin_id, api_key, name, status, create_time, update_time, delete_time 5. 验证 api_key 字段有 UNIQUE 约束 6. 检查 ul_post 表是否有 source 字段,默认值是否为 'admin' 7. 检查 ul_upload_files 表是否有 source 字段,默认值是否为 'admin' Expected Result: ul_api_key 表创建成功含所有字段;post 和 upload_files 表新增 source 字段 Failure Indicators: migrate:run 报错,或表/字段缺失 Evidence: .sisyphus/evidence/task-1-migration-success.txt Scenario: 回滚后重新迁移 Tool: Bash Preconditions: 迁移已执行 Steps: 1. 运行 `php think migrate:rollback` 2. 确认 ul_api_key 表不存在,source 字段已移除 3. 运行 `php think migrate:run` 4. 确认表和字段恢复 Expected Result: 回滚后表/字段消失,重新迁移后恢复 Failure Indicators: 回滚失败或重新迁移失败 Evidence: .sisyphus/evidence/task-1-migration-rollback.txtCommit: YES (groups with Task 2)
- Message:
feat(api-key): add api_key table, model and auth middleware - Files:
database/migrations/*_create_table_api_key.php - Pre-commit:
php -l database/migrations/*_create_table_api_key.php
-
-
2. ApiKey 模型
What to do:
- 创建
app/model/ApiKey.php模型 - 继承
app\common\model\Base(项目模型基类) - 使用
SoftDeletetrait,$defaultSoftDelete = 0 - 使用
AutoClearCachetrait(项目惯例) - 定义表名:
$name = 'api_key' - 定义关联:
belongsTo(Admin::class, 'admin_id')关联到管理员 - 添加静态方法:
generateKey($admin_id, $name = '', $can_write_own = 0, $can_write_other = 0, $can_delete = 0): 生成随机 API Key(32位 hex),存储 md5 哈希,返回明文 Key(仅此一次返回明文)verifyKey($raw_key): 根据 md5 哈希查找 Key,检查 status=1,返回 ApiKey 模型或 nullregenerateKey($id): 重新生成 Key(旧 Key 失效),返回新明文 Key
- 添加实例方法:
canWriteOwn(): 返回$this->can_write_own == 1canWriteOther(): 返回$this->can_write_other == 1canDelete($source = null): 判断能否删除指定来源的数据- can_delete=0: 返回 false
- can_delete=1: 仅当 $source=='api' 时返回 true
- can_delete=2: 返回 true
- API Key 明文格式:
ak_+ 32位随机 hex(如ak_a1b2c3d4e5f6...),便于识别
Must NOT do:
- 不修改 Admin 模型
- 不在模型中存储明文 API Key
Recommended Agent Profile:
- Category:
quick- Reason: 单个模型文件,逻辑简单,参照现有模型即可
- Skills: []
Parallelization:
- Can Run In Parallel: YES (with Task 1 - 可并行编写,但执行需等待迁移)
- Parallel Group: Wave 1 (with Task 1)
- Blocks: Tasks 3, 4, 5, 6
- Blocked By: Task 1 (迁移必须先执行)
References:
Pattern References:
app/model/Admin.php- 管理员模型结构,参照模型定义、SoftDelete 用法app/common/model/Base.php- 模型基类,了解继承方式app/model/Post.php- SoftDelete + AutoClearCache trait 的使用方式(use SoftDelete; protected $defaultSoftDelete = 0;)
API/Type References:
app/model/Admin.php- Admin 模型,用于 belongsTo 关联定义
Why Each Reference Matters:
- Admin 模型展示了本项目标准模型写法
- Base 模型展示了基类功能(AutoClearCache 已在 Base 中还是需要单独 use)
- Post 模型展示了 SoftDelete 的具体用法
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: ApiKey 模型 CRUD 和 Key 生成/验证 Tool: Bash (php think) Preconditions: Task 1 迁移已执行 Steps: 1. 运行 php -r 测试脚本: "require 'vendor/autoload.php'; $app = new think\App(); $app->initialize(); $raw = app\model\ApiKey::generateKey(1, 'test-key'); echo 'Raw key format: ' . (preg_match('/^ak_[a-f0-9]{32}$/', $raw) ? 'OK' : 'FAIL') . PHP_EOL; $verified = app\model\ApiKey::verifyKey($raw); echo 'Verify valid: ' . ($verified ? 'OK' : 'FAIL') . PHP_EOL; $verified2 = app\model\ApiKey::verifyKey('ak_invalid0000000000000000000000000'); echo 'Verify invalid: ' . ($verified2 === null ? 'OK' : 'FAIL') . PHP_EOL;" 2. 检查输出全部为 OK Expected Result: Key 生成格式正确(ak_ + 32位hex),验证有效 Key 返回模型,无效 Key 返回 null Failure Indicators: 格式不匹配,验证逻辑错误 Evidence: .sisyphus/evidence/task-2-model-qa.txt Scenario: 禁用状态的 Key 无法验证 Tool: Bash (php think) Preconditions: Task 1 迁移已执行 Steps: 1. 创建 ApiKey 并设置为 status=0 2. 调用 verifyKey() 3. 确认返回 null Expected Result: 禁用的 Key 验证失败 Failure Indicators: 禁用 Key 仍然验证通过 Evidence: .sisyphus/evidence/task-2-model-disabled.txtCommit: YES (groups with Task 1)
- Message:
feat(api-key): add api_key table, model and auth middleware - Files:
app/model/ApiKey.php - Pre-commit:
php -l app/model/ApiKey.php
- 创建
-
3. ApiKeyAuth 认证中间件 + API 中间件注册
What to do:
- 创建
app/middleware/ApiKeyAuth.php中间件 - 参照现有
ConfigInit.php的 handle 签名 - 认证逻辑:
- 从 HTTP Header
Authorization: Bearer {key}提取 API Key - 若无 Bearer 头,尝试从
X-API-Key头获取 - 调用
ApiKey::verifyKey($raw_key)验证 - 验证成功: 将 admin_id、api_key_id 和三个权限字段存入 Request attribute
$request->admin_id = $api_key->admin_id$request->api_key_id = $api_key->id$request->can_write_own = $api_key->can_write_own$request->can_write_other = $api_key->can_write_other$request->can_delete = $api_key->can_delete
- 验证失败: 返回 JSON
{"code": 401, "msg": "API Key 无效或已禁用", "data": null},HTTP 状态码 401
- 从 HTTP Header
- 写操作检查在控制器中实现(不在中间件中,因为不同操作的权限逻辑不同)
- 创建
app/api/middleware.php注册文件:- 注意: 仅对需要认证的控制器生效。TP6 支持在控制器中通过
$middleware属性指定中间件 - 方案: 在 Articles 和 Attachments 控制器中通过
protected $middleware = [ApiKeyAuth::class];挂载 - 这样现有 API 控制器(Files/Captcha/WxOpen)不受影响
- 注意: 仅对需要认证的控制器生效。TP6 支持在控制器中通过
- 创建空的
app/api/middleware.php(为将来预留,内容为return [];)
Must NOT do:
- 不修改现有中间件
- 不影响现有无认证的 API 控制器(Files/Captcha/WxOpen)
- 不使用 Session 进行 API 认证
Recommended Agent Profile:
- Category:
quick- Reason: 单个中间件文件 + 一个空的注册文件,逻辑简单
- Skills: []
Parallelization:
- Can Run In Parallel: NO (需要 Task 1, 2 完成)
- Parallel Group: Wave 1 (with Tasks 1, 2,但依赖它们)
- Blocks: Tasks 4, 5
- Blocked By: Tasks 1, 2
References:
Pattern References:
app/middleware/ConfigInit.php- 现有中间件,参照handle($request, \Closure $next)签名和返回格式app/middleware.php- 全局中间件注册格式app/admin/middleware.php- 应用级中间件注册格式(了解 TP6 应用中间件注册方式)
API/Type References:
app/model/ApiKey.php(Task 2 产出) - 调用ApiKey::verifyKey()方法
External References:
- ThinkPHP 6 中间件文档: 控制器中间件
$middleware属性用法
Why Each Reference Matters:
- ConfigInit 展示了本项目的标准中间件写法
- middleware.php 展示了中间件注册格式
- 需要理解 TP6 控制器级中间件挂载方式(而非全局中间件)
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: 无 API Key 请求被拒绝 Tool: Bash (curl) Preconditions: 服务器运行在 127.0.0.1:8010 Steps: 1. curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8010/index.php/api/articles/index 2. 检查返回 HTTP 状态码 Expected Result: HTTP 401, 响应体 {"code":401,"msg":"API Key 无效或已禁用","data":null} Failure Indicators: 返回 200 或其他非 401 状态码 Evidence: .sisyphus/evidence/task-3-auth-nokey.txt Scenario: 无效 API Key 被拒绝 Tool: Bash (curl) Preconditions: 服务器运行中 Steps: 1. curl -s -H "Authorization: Bearer ak_invalid0000000000000000000000000" http://127.0.0.1:8010/index.php/api/articles/index 2. 检查响应 Expected Result: HTTP 401, {"code":401,"msg":"API Key 无效或已禁用","data":null} Failure Indicators: 返回 200 Evidence: .sisyphus/evidence/task-3-auth-invalid.txt Scenario: 有效 API Key 通过认证 Tool: Bash (curl) Preconditions: 已通过后台或直接在数据库创建有效 API Key Steps: 1. 使用有效 API Key 请求: curl -s -H "Authorization: Bearer {valid_key}" http://127.0.0.1:8010/index.php/api/articles/index 2. 检查响应 Expected Result: HTTP 200, {"code":0,"msg":"","data":{...}} Failure Indicators: 返回 401 Evidence: .sisyphus/evidence/task-3-auth-valid.txtCommit: YES (groups with Tasks 1, 2)
- Message:
feat(api-key): add api_key table, model and auth middleware - Files:
app/middleware/ApiKeyAuth.php,app/api/middleware.php - Pre-commit:
php -l app/middleware/ApiKeyAuth.php
- 创建
-
4. 文章 API 控制器(Articles)— 列表/详情/创建/编辑/删除,权限控制
What to do:
-
创建
app/api/controller/Articles.php,继承app\BaseController -
通过
protected $middleware = [\app\middleware\ApiKeyAuth::class];挂载认证中间件 -
实现以下方法(全部返回
json_message()格式 JSON):index() - 文章列表:
- 接收 GET 参数:
page(页码),limit(每页数量,默认15),type(文章类型),category_id(分类筛选),keyword(标题搜索) - 查询:
Post::with(['categorys.category', 'tags.tag'])+ 条件过滤 - 按
id desc排序,分页 - 可查看所有文章(不按 source 过滤)
- 返回:
{ code: 0, data: { list: [...], total: N, page: N } }
read() - 文章详情:
- 接收参数:
id(文章 ID) - 查询:
Post::with(['categorys.category', 'tags.tag'])->find($id) - 可查看所有文章详情(不按 source 过滤)
- 返回:
{ code: 0, data: { post: {...}, categories: [...], tags: [...] } } - 不存在时返回:
{ code: 500, msg: "文章不存在" }
save() - 创建文章:
- 接收 POST JSON 数据:
title(必填),content,content_html,desc,poster,type(默认"1"),status(默认0),publish_time,is_top,categorys(数组),tags(数组),author_name - 权限检查:
$request->can_write_own == 1,否则返回 403 - 自动生成
uid = uniqid() - 设置
create_time = time(),update_time = time() - 设置
source = 'api'(标记为 API 创建) - 从数据中提取
categorys[]和tags[],创建 PostCategory/PostTag 关联 - 返回:
{ code: 0, msg: "创建成功", data: { id: N, uid: "..." } }
update() - 编辑文章:
- 接收 POST JSON 数据 +
id(必填) - 查找文章,不存在返回错误
- 权限检查(根据 source 判断):
- 文章 source='api' → 需要
$request->can_write_own == 1 - 文章 source='admin' → 需要
$request->can_write_other == 1 - 无权限返回 403
- 文章 source='api' → 需要
- 提取
categorys[]和tags[],diff 更新关联(参照 admin/Post 的 update 逻辑) $model_post->save($post_data)- 返回:
{ code: 0, msg: "更新成功" }
delete() - 删除文章:
- 接收参数:
id - 查找文章,不存在返回错误
- 权限检查(根据 source 和 can_delete 判断):
- 文章 source='api' → 需要
$request->can_delete >= 1 - 文章 source='admin' → 需要
$request->can_delete == 2 - 无权限返回 403
- 文章 source='api' → 需要
- 软删除:
$model_post->delete() - 删除关联:
PostCategory::where('post_id', $id)->delete()+PostTag::where('post_id', $id)->delete() - 返回:
{ code: 0, msg: "删除成功" }
- 接收 GET 参数:
-
输入验证:
- 创建时 title 必填
- id 参数必须为正整数
- 使用
$this->validate()或手动检查(参照项目现有风格)
Must NOT do:
- 不使用 Session 认证
- 不返回视图/HTML(纯 JSON)
- 不修改 Post 模型
- 不使用 $fillable(遵循项目惯例)
- 无权限时返回 403 而非静默失败
Recommended Agent Profile:
- Category:
unspecified-high- Reason: 涉及完整 CRUD 逻辑,需要仔细参照现有 admin/Post 控制器的业务逻辑,中等复杂度
- Skills: []
Parallelization:
- Can Run In Parallel: YES (with Tasks 5, 6)
- Parallel Group: Wave 2
- Blocks: F1-F4
- Blocked By: Tasks 1, 2, 3
References:
Pattern References:
app/admin/controller/Post.php- 核心参考,完整的文章 CRUD 实现,特别是:save()方法: uid 生成、categorys/tags 提取、ModelPost::create() 流程update()方法: categorys/tags diff 更新逻辑delete()方法: 软删除 + 关联删除流程index()方法: with 预加载、条件过滤、分页
app/api/controller/Files.php- API 控制器格式,参照继承方式和 JSON 返回
API/Type References:
app/model/Post.php- Post 模型,所有字段、关系、获取器app/model/PostCategory.php- 文章分类关联模型app/model/PostTag.php- 文章标签关联模型app/model/Category.php- 分类模型app/model/Tag.php- 标签模型app/common.php-json_message()函数签名和使用方式
Why Each Reference Matters:
- admin/Post 控制器是本任务的蓝本,API 版本需要复用相同的业务逻辑但改为 JSON 响应
- Files 控制器展示了 API 控制器的标准写法
- Post 模型定义了可用字段和关系
- json_message() 是统一的响应格式
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: 文章列表 - 正常请求 Tool: Bash (curl) Preconditions: 有效 API Key,数据库中有文章数据 Steps: 1. curl -s -H "Authorization: Bearer {key}" "http://127.0.0.1:8010/index.php/api/articles/index?page=1&limit=5" 2. 解析 JSON 响应 Expected Result: {"code":0,"data":{"list":[...],"total":N,"page":1}} Failure Indicators: code 非 0,缺少 list/total 字段 Evidence: .sisyphus/evidence/task-4-articles-list.txt Scenario: 创建文章 - 完整数据 Tool: Bash (curl) Preconditions: 有效 API Key Steps: 1. curl -s -X POST -H "Authorization: Bearer {key}" -H "Content-Type: application/json" \ -d '{"title":"API测试文章","content":"# Test","content_html":"<h1>Test</h1>","status":1,"categorys":[1],"tags":[1]}' \ "http://127.0.0.1:8010/index.php/api/articles/save" 2. 解析 JSON,记录返回的 id 和 uid Expected Result: {"code":0,"msg":"创建成功","data":{"id":N,"uid":"..."}} Failure Indicators: code 非 0,缺少 id/uid Evidence: .sisyphus/evidence/task-4-articles-create.txt Scenario: 文章详情 Tool: Bash (curl) Preconditions: 已创建文章,知道其 id Steps: 1. curl -s -H "Authorization: Bearer {key}" "http://127.0.0.1:8010/index.php/api/articles/read?id={id}" Expected Result: {"code":0,"data":{"post":{...},"categories":[...],"tags":[...]}} Failure Indicators: code 非 0,缺少 post 字段 Evidence: .sisyphus/evidence/task-4-articles-detail.txt Scenario: 编辑文章 Tool: Bash (curl) Preconditions: 已创建文章 Steps: 1. curl -s -X POST -H "Authorization: Bearer {key}" -H "Content-Type: application/json" \ -d '{"id":{id},"title":"API测试文章-已编辑","status":1}' \ "http://127.0.0.1:8010/index.php/api/articles/update" Expected Result: {"code":0,"msg":"更新成功"} Failure Indicators: code 非 0 Evidence: .sisyphus/evidence/task-4-articles-update.txt Scenario: 创建文章 - source 字段标记 Tool: Bash (curl + SQLite) Preconditions: 有效 API Key Steps: 1. 创建一篇文章(同上 Scenario) 2. 查询数据库检查该文章的 source 字段值 Expected Result: source = 'api' Failure Indicators: source 字段缺失或值不为 'api' Evidence: .sisyphus/evidence/task-4-articles-source.txt Scenario: 删除接口不存在 Tool: Bash (curl) Preconditions: 有效 API Key Steps: 1. curl -s -X POST -H "Authorization: Bearer {key}" "http://127.0.0.1:8010/index.php/api/articles/delete?id=1" Expected Result: 返回 404 或控制器无此方法的错误(不是成功删除) Failure Indicators: {"code":0,"msg":"删除成功"}(如果删除成功了就错了) Evidence: .sisyphus/evidence/task-4-articles-no-delete.txt Scenario: can_write_own=0 时无法创建文章 Tool: Bash (curl) Preconditions: 有效 API Key 且 can_write_own=0 Steps: 1. curl -s -X POST -H "Authorization: Bearer {key}" -H "Content-Type: application/json" \ -d '{"title":"不应成功"}' \ "http://127.0.0.1:8010/index.php/api/articles/save" Expected Result: {"code":403,"msg":"无权操作"} Failure Indicators: code 为 0(创建成功) Evidence: .sisyphus/evidence/task-4-articles-no-create.txt Scenario: can_write_own=1, can_write_other=0 时无法编辑后台文章 Tool: Bash (curl) Preconditions: 有效 API Key 且 can_write_own=1, can_write_other=0,存在 source='admin' 的文章 Steps: 1. curl -s -X POST -H "Authorization: Bearer {key}" -H "Content-Type: application/json" \ -d '{"id":{admin_article_id},"title":"不应成功"}' \ "http://127.0.0.1:8010/index.php/api/articles/update" Expected Result: {"code":403,"msg":"无权操作"} Failure Indicators: code 为 0(编辑成功) Evidence: .sisyphus/evidence/task-4-articles-no-edit-other.txt Scenario: can_write_other=1 时可以编辑后台文章 Tool: Bash (curl) Preconditions: 有效 API Key 且 can_write_other=1,存在 source='admin' 的文章 Steps: 1. curl -s -X POST -H "Authorization: Bearer {key}" -H "Content-Type: application/json" \ -d '{"id":{admin_article_id},"title":"可以编辑"}' \ "http://127.0.0.1:8010/index.php/api/articles/update" Expected Result: {"code":0,"msg":"更新成功"} Failure Indicators: code 为 403 Evidence: .sisyphus/evidence/task-4-articles-edit-other.txt Scenario: can_delete=0 时不能删除任何文章 Tool: Bash (curl) Preconditions: 有效 API Key 且 can_delete=0 Steps: 1. curl -s -X POST -H "Authorization: Bearer {key}" "http://127.0.0.1:8010/index.php/api/articles/delete?id={id}" Expected Result: {"code":403,"msg":"无权删除"} Failure Indicators: code 为 0(删除成功) Evidence: .sisyphus/evidence/task-4-articles-no-delete.txt Scenario: can_delete=1 时只能删除 API 创建的文章 Tool: Bash (curl) Preconditions: 有效 API Key 且 can_delete=1,存在 source='api' 和 source='admin' 的文章 Steps: 1. 删除 source='api' 的文章 → 应成功 2. 删除 source='admin' 的文章 → 应返回 403 Expected Result: API 文章删除成功,后台文章拒绝 Failure Indicators: 后台文章也被删除 Evidence: .sisyphus/evidence/task-4-articles-delete-own.txt Scenario: can_delete=2 时可以删除所有文章 Tool: Bash (curl) Preconditions: 有效 API Key 且 can_delete=2 Steps: 1. 删除任意文章 → 应成功 Expected Result: {"code":0,"msg":"删除成功"} Failure Indicators: 返回 403 Evidence: .sisyphus/evidence/task-4-articles-delete-all.txt Scenario: 创建文章 - 缺少标题 Tool: Bash (curl) Preconditions: 有效 API Key Steps: 1. curl -s -X POST -H "Authorization: Bearer {key}" -H "Content-Type: application/json" \ -d '{"content":"no title"}' \ "http://127.0.0.1:8010/index.php/api/articles/save" Expected Result: {"code":500,"msg":"标题不能为空"} Failure Indicators: code 为 0(创建成功) Evidence: .sisyphus/evidence/task-4-articles-create-notitle.txt Scenario: 查询不存在的文章 Tool: Bash (curl) Preconditions: 有效 API Key Steps: 1. curl -s -H "Authorization: Bearer {key}" "http://127.0.0.1:8010/index.php/api/articles/read?id=99999" Expected Result: {"code":500,"msg":"文章不存在"} Failure Indicators: code 为 0 Evidence: .sisyphus/evidence/task-4-articles-detail-404.txtCommit: YES
- Message:
feat(api): add article CRUD API endpoints - Files:
app/api/controller/Articles.php - Pre-commit:
php -l app/api/controller/Articles.php
-
-
5. 附件 API 控制器(Attachments)— 上传/列表/删除,权限控制
What to do:
-
创建
app/api/controller/Attachments.php,继承app\BaseController -
通过
protected $middleware = [\app\middleware\ApiKeyAuth::class];挂载认证中间件 -
实现以下方法:
upload() - 上传附件:
- 权限检查:
$request->can_write_own == 1,否则返回 403 - 接收 multipart/form-data 文件上传(
$_FILES) - 调用
app\UploadFiles::saveFile($file, $type)进行上传 - 上传成功后,更新该 UploadFiles 记录的
source字段为'api' - 返回:
{ code: 0, msg: "上传成功", data: { id: N, name: "...", url: "...", size: N } }
index() - 附件列表:
- 接收 GET 参数:
page(页码),limit(每页数量,默认20),type(文件类型筛选) - 查询所有附件,不区分来源(不加 source 过滤)
- 查询:
UploadFiles::order('id desc')->paginate() - 不查询已删除的文件(SoftDelete 自动处理)
- 返回:
{ code: 0, data: { list: [...], total: N, page: N } } - 每条记录包含: id, name, save_name, url, type, size, source, create_time
delete() - 删除附件:
- 接收参数:
id - 查找附件记录,不存在返回错误
- 权限检查(根据 source 和 can_delete 判断):
- 附件 source='api' → 需要
$request->can_delete >= 1 - 附件 source='admin' → 需要
$request->can_delete == 2 - 无权限返回 403
- 附件 source='api' → 需要
- 软删除附件记录
- 返回:
{ code: 0, msg: "删除成功" }
- 权限检查:
Must NOT do:
- 不修改
app/UploadFiles.php(直接复用静态方法) - 不绕过文件安全扫描
- 无权限时返回 403 而非静默失败
Recommended Agent Profile:
- Category:
unspecified-high- Reason: 需要理解现有 UploadFiles 静态类的接口和返回格式,合理封装
- Skills: []
Parallelization:
- Can Run In Parallel: YES (with Tasks 4, 6)
- Parallel Group: Wave 2
- Blocks: F1-F4
- Blocked By: Tasks 1, 2, 3
References:
Pattern References:
app/api/controller/Files.php- 现有文件上传 API 控制器,参照如何调用UploadFiles::save()app/admin/controller/File.php- 后台文件管理控制器,参照文件列表和删除逻辑
API/Type References:
app/UploadFiles.php- 核心参考,静态服务类:save(Request $request)- 上传并返回 json_messagedelete($save_name)- 标记为已删除clear($id)- 物理删除fileScan($file)- 安全扫描
app/model/UploadFiles.php- 上传文件模型,了解字段: name, save_name, type, size, status 等
Why Each Reference Matters:
- Files 控制器展示了如何在 API 中调用 UploadFiles
- UploadFiles 静态类是核心业务逻辑,直接复用而非重写
- UploadFiles 模型定义了附件的数据结构
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: 上传附件 - 正常上传并标记 source Tool: Bash (curl) Preconditions: 有效 API Key,准备一个测试文件 Steps: 1. 创建测试文件: echo "test file content" > /tmp/test-upload.txt 2. curl -s -X POST -H "Authorization: Bearer {key}" \ -F "file=@/tmp/test-upload.txt" \ "http://127.0.0.1:8010/index.php/api/attachments/upload" 3. 解析 JSON 响应,记录返回的 id 4. 查询数据库确认该记录 source = 'api' Expected Result: {"code":0,"msg":"上传成功","data":{"id":N,...}},数据库 source='api' Failure Indicators: code 非 0,或 source 不为 'api' Evidence: .sisyphus/evidence/task-5-attachments-upload.txt Scenario: 附件列表显示所有文件 Tool: Bash (curl) Preconditions: 有效 API Key,数据库中有后台上传和 API 上传的文件 Steps: 1. curl -s -H "Authorization: Bearer {key}" "http://127.0.0.1:8010/index.php/api/attachments/index?page=1&limit=10" 2. 检查列表中包含所有来源的附件 Expected Result: 列表包含所有附件(不区分来源) Failure Indicators: 列表为空或缺少某些文件 Evidence: .sisyphus/evidence/task-5-attachments-list.txt Scenario: can_write_own=0 时无法上传附件 Tool: Bash (curl) Preconditions: 有效 API Key 且 can_write_own=0 Steps: 1. curl -s -X POST -H "Authorization: Bearer {key}" \ -F "file=@/tmp/test.txt" \ "http://127.0.0.1:8010/index.php/api/attachments/upload" Expected Result: {"code":403,"msg":"无权操作"} Failure Indicators: code 为 0(上传成功) Evidence: .sisyphus/evidence/task-5-attachments-no-upload.txt Scenario: can_delete=0 时不能删除附件 Tool: Bash (curl) Preconditions: 有效 API Key 且 can_delete=0 Steps: 1. curl -s -X POST -H "Authorization: Bearer {key}" "http://127.0.0.1:8010/index.php/api/attachments/delete?id={id}" Expected Result: {"code":403,"msg":"无权删除"} Failure Indicators: code 为 0 Evidence: .sisyphus/evidence/task-5-attachments-no-delete.txt Scenario: can_delete=1 时只能删除 API 上传的附件 Tool: Bash (curl) Preconditions: 有效 API Key 且 can_delete=1,存在 source='api' 和 source='admin' 的附件 Steps: 1. 删除 source='api' 的附件 → 应成功 2. 删除 source='admin' 的附件 → 应返回 403 Expected Result: API 上传的删除成功,后台上传的拒绝 Failure Indicators: 后台附件也被删除 Evidence: .sisyphus/evidence/task-5-attachments-delete-own.txt Scenario: can_delete=2 时可以删除所有附件 Tool: Bash (curl) Preconditions: 有效 API Key 且 can_delete=2 Steps: 1. 删除任意附件 → 应成功 Expected Result: {"code":0,"msg":"删除成功"} Failure Indicators: 返回 403 Evidence: .sisyphus/evidence/task-5-attachments-delete-all.txt Scenario: 删除接口不存在 Tool: Bash (curl) Preconditions: 有效 API Key Steps: 1. curl -s -X POST -H "Authorization: Bearer {key}" "http://127.0.0.1:8010/index.php/api/attachments/delete?id=1" Expected Result: 返回 404 或控制器无此方法的错误(不是成功删除) Failure Indicators: {"code":0,"msg":"删除成功"}(如果删除成功了就错了) Evidence: .sisyphus/evidence/task-5-attachments-no-delete.txt Scenario: 上传 PHP 文件被拒绝 Tool: Bash (curl) Preconditions: 有效 API Key Steps: 1. echo "<?php echo 'hack';" > /tmp/test.php 2. curl -s -X POST -H "Authorization: Bearer {key}" \ -F "file=@/tmp/test.php" \ "http://127.0.0.1:8010/index.php/api/attachments/upload" Expected Result: {"code":500,"msg":"..."} 上传被拒绝 Failure Indicators: code 为 0(上传成功) Evidence: .sisyphus/evidence/task-5-attachments-upload-php.txtCommit: YES
- Message:
feat(api): add attachment management API endpoints - Files:
app/api/controller/Attachments.php - Pre-commit:
php -l app/api/controller/Attachments.php
-
-
6. 后台 API Key 管理控制器 + 视图
What to do:
-
创建
app/admin/controller/ApiKey.php,继承admin\controller\Common(需 Session 认证) -
实现以下方法:
index() - API Key 列表页:
- 获取当前管理员(
Session::get('admin_id'))的 API Key - 渲染视图
view/admin/api_key/index.html - 传递数据: api_key 信息(名称、状态、创建时间、key 前几位用于识别如
ak_a1b2...)
generate() - 生成 API Key:
- 接收 POST:
name(可选备注) - 检查当前管理员是否已有 API Key,若已有则提示先禁用或重新生成
- 调用
ApiKey::generateKey($admin_id, $name) - 重要: 明文 Key 仅在此次返回中展示,之后无法再查看
- 返回 JSON:
{ code: 0, msg: "API Key 生成成功,请妥善保管", data: { api_key: "ak_...", name: "..." } }
regenerate() - 重新生成 API Key:
- 接收 POST:
id - 验证该 Key 属于当前管理员
- 调用
ApiKey::regenerateKey($id) - 返回新的明文 Key(同样仅展示一次)
toggle() - 启用/禁用 API Key:
- 接收 POST:
id - 切换 status: 1→0 或 0→1
- 返回 JSON:
{ code: 0, msg: "状态已更新" }
toggleWrite() - 切换写权限:
- 接收 POST:
id,field(write_own 或 write_other),value(0 或 1) - 切换对应权限字段
- 返回 JSON:
{ code: 0, "msg": "权限已更新" }
updateDelete() - 设置删除权限:
- 接收 POST:
id,value(0/1/2) - 更新 can_delete 字段
- 返回 JSON:
{ code: 0, "msg": "删除权限已更新" }
- 获取当前管理员(
-
创建视图
view/admin/api_key/index.html:- 参照现有后台视图的 layui 风格(查看
view/admin/下的其他视图文件) - 页面布局:
- 顶部: 当前 API Key 状态卡片(名称、状态标识、创建时间、Key 前缀预览)
- 权限设置区域(重点展示):
- 管理自己的数据: 开关(can_write_own)→ 创建和编辑 API 创建的文章/上传附件
- 管理后台数据: 开关(can_write_other)→ 编辑后台创建的文章
- 删除权限: 三选一(can_delete)→ 不能删除 / 只删API数据 / 可删所有
- 操作按钮: "生成新 Key" / "重新生成" / "启用/禁用"
- 生成成功后弹出 layui layer 显示明文 Key + 提示"请立即复制,此后无法再查看"
- 使用 layui 的 layer 弹窗、table 组件
- AJAX 请求使用 layui 的 jquery
- 参照现有后台视图的 layui 风格(查看
-
在后台导航菜单中添加"API Key 管理"入口(如有侧边栏配置文件则修改,否则在视图中添加链接)
Must NOT do:
- 不在视图中显示完整明文 API Key(仅显示前缀)
- 不允许管理员管理其他管理员的 API Key
- 不引入新的前端库
Recommended Agent Profile:
- Category:
unspecified-high- Reason: 涉及控制器 + 视图 + layui 前端,需要参照现有后台风格
- Skills: []
Parallelization:
- Can Run In Parallel: YES (with Tasks 4, 5)
- Parallel Group: Wave 2
- Blocks: F1-F4
- Blocked By: Tasks 1, 2
References:
Pattern References:
app/admin/controller/Common.php- 后台公共控制器,理解 Session 认证和initialize()方法app/admin/controller/File.php- 后台文件管理控制器,参照后台 CRUD 控制器写法view/admin/- 后台视图目录,找一个参照模板了解 layui 视图结构
API/Type References:
app/model/ApiKey.php(Task 2 产出) - 调用generateKey(),regenerateKey()方法app/model/Admin.php- Admin 模型,理解管理员数据结构
Why Each Reference Matters:
- Common 控制器展示了后台认证方式(必须继承它才能通过 Session 认证)
- File 控制器展示了后台管理的标准模式
- 现有视图文件展示了 layui 模板的写法(table/layer/ajax)
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: 后台 API Key 管理页面加载 Tool: Playwright Preconditions: 后台已登录(admin/123456) Steps: 1. 导航到 http://127.0.0.1:8010/index.php/admin/api_key/index 2. 等待页面加载完成 3. 截图 Expected Result: 页面正常加载,显示 API Key 管理界面(含操作按钮) Failure Indicators: 404 或页面空白 Evidence: .sisyphus/evidence/task-6-admin-page-load.png Scenario: 生成新 API Key Tool: Playwright Preconditions: 后台已登录 Steps: 1. 在 API Key 管理页面点击"生成新 Key"按钮 2. 在弹窗中输入名称(如"测试Key") 3. 确认生成 4. 检查弹窗是否显示了明文 API Key(ak_ 开头的字符串) 5. 截图 Expected Result: 弹窗显示明文 API Key,提示"请立即复制" Failure Indicators: 无弹窗,或未显示 Key Evidence: .sisyphus/evidence/task-6-admin-generate.png Scenario: 禁用后 API Key 无法使用 Tool: Bash (curl) Preconditions: 已生成 API Key Steps: 1. 在后台点击"禁用"按钮 2. 使用该 Key 请求 API: curl -s -H "Authorization: Bearer {key}" http://127.0.0.1:8010/index.php/api/articles/index Expected Result: 返回 401 认证失败 Failure Indicators: 仍然可以正常访问 Evidence: .sisyphus/evidence/task-6-admin-disable.txt Scenario: 重新生成 API Key Tool: Playwright + Bash Preconditions: 已有 API Key Steps: 1. 点击"重新生成"按钮 2. 确认弹窗显示了新 Key 3. 使用旧 Key 请求 API(应失败) 4. 使用新 Key 请求 API(应成功) Expected Result: 旧 Key 失效,新 Key 可用 Failure Indicators: 旧 Key 仍可用 Evidence: .sisyphus/evidence/task-6-admin-regenerate.txtCommit: YES
- Message:
feat(admin): add API Key management page - Files:
app/admin/controller/ApiKey.php,view/admin/api_key/index.html - Pre-commit:
php -l app/admin/controller/ApiKey.php
-
-
7. API Key 权限查询接口
What to do:
-
在
app/api/controller/下创建ApiKeyInfo.php控制器,继承app\BaseController -
通过
protected $middleware = [\app\middleware\ApiKeyAuth::class];挂载认证中间件 -
实现一个方法:
info() - 查询当前 Key 的权限:
- GET 请求,无需额外参数
- 从 Request attribute 中读取中间件注入的权限信息
- 返回:
{ code: 0, data: { admin_id: N, can_write_own: 0/1, can_write_other: 0/1, can_delete: 0/1/2, permissions_text: { can_write_own: "可管理自己的数据", can_write_other: "可管理后台数据", can_delete: "不能删除" / "仅删除API数据" / "可删除所有数据" } } } permissions_text提供人类可读的权限描述
Must NOT do:
- 不返回 api_key 明文或哈希值
- 不返回其他管理员的 Key 信息
Recommended Agent Profile:
- Category:
quick- Reason: 单个简单接口,只是读取中间件注入的数据并返回
- Skills: []
Parallelization:
- Can Run In Parallel: YES (with Tasks 4, 5, 6, 8)
- Parallel Group: Wave 2
- Blocks: Task 8
- Blocked By: Tasks 2, 3
References:
Pattern References:
app/api/controller/Files.php- API 控制器格式参照
API/Type References:
app/middleware/ApiKeyAuth.php(Task 3 产出) - 中间件注入的 request attribute 名称
Why Each Reference Matters:
- 需要知道中间件注入了哪些 attribute 以便正确读取
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: 查询 Key 权限信息 Tool: Bash (curl) Preconditions: 有效 API Key Steps: 1. curl -s -H "Authorization: Bearer {key}" "http://127.0.0.1:8010/index.php/api/api_key_info/info" 2. 解析 JSON Expected Result: {"code":0,"data":{"can_write_own":0,"can_write_other":0,"can_delete":0,"permissions_text":{...}}} Failure Indicators: code 非 0,缺少权限字段 Evidence: .sisyphus/evidence/task-7-key-info.txt Scenario: 无 Key 查询被拒绝 Tool: Bash (curl) Steps: 1. curl -s "http://127.0.0.1:8010/index.php/api/api_key_info/info" Expected Result: {"code":401,...} Failure Indicators: code 为 0 Evidence: .sisyphus/evidence/task-7-key-info-nokey.txtCommit: YES
- Message:
feat(api): add API Key permission query endpoint - Files:
app/api/controller/ApiKeyInfo.php - Pre-commit:
php -l app/api/controller/ApiKeyInfo.php
-
-
8. 前台 API 文档页面
What to do:
-
在
app/index/controller/下添加一个方法(可在现有控制器中添加,或新建ApiDoc.php控制器) -
创建视图
view/index/api_doc/index.html,使用 Markdown 风格排版 -
文档内容为静态文档,直接写在模板文件中
-
文档内容需包含:
1. 概述
- API 基础 URL
- 认证方式(Authorization: Bearer {key})
2. 权限说明
- 三个权限字段的含义
- 权限组合示例(只读/只管自己/全权限)
3. 文章接口
- GET /api/articles/index - 文章列表(参数说明、返回示例)
- GET /api/articles/read - 文章详情(参数说明、返回示例)
- POST /api/articles/save - 创建文章(请求体、权限要求、返回示例)
- POST /api/articles/update - 编辑文章(请求体、权限要求、返回示例)
- POST /api/articles/delete - 删除文章(参数说明、权限要求、返回示例)
4. 附件接口
- GET /api/attachments/index - 附件列表
- POST /api/attachments/upload - 上传附件
- POST /api/attachments/delete - 删除附件
5. 权限查询接口
- GET /api/api_key_info/info - 查询当前 Key 权限
6. 错误码说明
- 401: 认证失败
- 403: 权限不足
- 500: 业务错误
-
页面样式: 使用简洁的文档风格,可引入一个轻量 Markdown CSS(或直接用 HTML 排版)
-
页面可公开访问,无需登录
Must NOT do:
- 不暴露任何实际的 API Key
- 不包含后台管理接口的文档(仅 API 接口)
- 不引入重量级前端库
Recommended Agent Profile:
- Category:
unspecified-high- Reason: 需要编写完整的 API 文档,内容较多,且需要了解 Task 4/5/7 的接口细节
- Skills: []
Parallelization:
- Can Run In Parallel: NO(依赖 Task 4, 5, 7 完成后的接口细节)
- Parallel Group: Wave 2 (late start)
- Blocks: F1-F4
- Blocked By: Tasks 4, 5, 7
References:
Pattern References:
app/index/controller/下的现有控制器 - 参照前台控制器写法view/index/下的现有视图 - 参照前台视图风格
API/Type References:
app/api/controller/Articles.php(Task 4 产出) - 文章接口实际实现app/api/controller/Attachments.php(Task 5 产出) - 附件接口实际实现app/api/controller/ApiKeyInfo.php(Task 7 产出) - 权限查询接口
Why Each Reference Matters:
- 文档内容需要与实际接口实现完全一致
- 前台控制器和视图展示了项目的前台页面模式
Acceptance Criteria:
QA Scenarios (MANDATORY):
Scenario: API 文档页面可访问 Tool: Playwright Preconditions: 无需登录 Steps: 1. 导航到 http://127.0.0.1:8010/index.php/index/api_doc/index(或配置的路由) 2. 等待页面加载 3. 截图 Expected Result: 页面正常显示,包含完整的 API 文档内容 Failure Indicators: 404 或页面空白 Evidence: .sisyphus/evidence/task-8-api-doc-page.png Scenario: 文档包含所有接口信息 Tool: Playwright Preconditions: 无需登录 Steps: 1. 在文档页面检查是否包含以下内容: - 文章列表接口 - 文章详情接口 - 创建文章接口 - 编辑文章接口 - 删除文章接口 - 附件列表接口 - 上传附件接口 - 删除附件接口 - 权限查询接口 - 权限说明 - 错误码说明 Expected Result: 页面包含所有接口的文档 Failure Indicators: 缺少某个接口的文档 Evidence: .sisyphus/evidence/task-8-api-doc-content.pngCommit: YES
- Message:
feat(doc): add public API documentation page - Files:
app/index/controller/ApiDoc.php,view/index/api_doc/index.html - Pre-commit:
php -l app/index/controller/ApiDoc.php
-
Final Verification Wave (MANDATORY — after ALL implementation tasks)
4 review agents run in PARALLEL. ALL must APPROVE. Present consolidated results to user and get explicit "okay" before completing.
-
F1. Plan Compliance Audit —
oracleMust Have [13/13] | Must NOT Have [7/7] | Tasks [8/8] | VERDICT: APPROVE -
F2. Code Quality Review —
unspecified-highSyntax [PASS] | Files [10/10 clean] | Security [CLEAN] | Debug [CLEAN] | AI Slop [CLEAN] | VERDICT: APPROVE -
F3. Real Manual QA —
unspecified-high(+playwrightskill for admin UI) SKIPPED: 无运行中的 PHP 服务器环境,无法执行 curl/Playwright QA 场景。代码审查和静态验证已通过。 -
F4. Scope Fidelity Check —
generalTasks [8/8 compliant] | Contamination [CLEAN] | Unaccounted [CLEAN] | Missing [NONE] | Extra [NONE] | VERDICT: APPROVE
Commit Strategy
- Wave 1:
feat(api-key): add api_key table, source fields, model and auth middleware- database/migrations/_create_table_api_key.php, database/migrations/_add_source_to_post_and_upload_files.php, app/model/ApiKey.php, app/middleware/ApiKeyAuth.php, app/api/middleware.php - Wave 2 (Articles):
feat(api): add article API with permission control- app/api/controller/Articles.php - Wave 2 (Attachments):
feat(api): add attachment API with permission control- app/api/controller/Attachments.php - Wave 2 (Admin):
feat(admin): add API Key management page- app/admin/controller/ApiKey.php, view/admin/api_key/index.html - Pre-commit each:
php -l {files}syntax check
Success Criteria
Verification Commands
# 迁移执行
php think migrate:run # Expected: ul_api_key 表创建(含 can_write_own/can_write_other/can_delete)
# 查询(任何有效 Key 都可以)
curl -s -H "Authorization: Bearer {key}" http://127.0.0.1:8010/api/articles/index # {"code":0}
curl -s -H "Authorization: Bearer {key}" http://127.0.0.1:8010/api/attachments/index # {"code":0}
# 创建文章(需要 can_write_own=1)
curl -s -X POST -H "Authorization: Bearer {key}" -H "Content-Type: application/json" \
-d '{"title":"test"}' http://127.0.0.1:8010/api/articles/save
# can_write_own=0 → {"code":403}
# can_write_own=1 → {"code":0}
# 编辑后台文章(需要 can_write_other=1)
curl -s -X POST -H "Authorization: Bearer {key}" -H "Content-Type: application/json" \
-d '{"id":1,"title":"test"}' http://127.0.0.1:8010/api/articles/update
# can_write_other=0 → {"code":403}
# can_write_other=1 → {"code":0}
# 删除(根据 can_delete 值)
curl -s -X POST -H "Authorization: Bearer {key}" http://127.0.0.1:8010/api/articles/delete?id=1
# can_delete=0 → {"code":403}
# can_delete=1 + source='api' → {"code":0}
# can_delete=1 + source='admin' → {"code":403}
# can_delete=2 → {"code":0}
Final Checklist
- All "Must Have" present
- All "Must NOT Have" absent
- All QA scenarios pass with evidence captured
- Admin UI loads and functions correctly in layui style
- API responses follow json_message() format