初始化

This commit is contained in:
2026-03-05 21:27:11 +08:00
commit 130de0fd5d
140 changed files with 21972 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
---
name: anti-scraping
version: 4.0.0
description: "为 PHP Hyperf + Vue 3 应用设计反爬虫防护。当需要防御 Bot、限流或保护 API 时使用。覆盖五个威胁层级。"
---
# 🛡️ Anti-Scraping Protection — 全栈防爬体系
## 威胁分级与对应策略
| 等级 | 爬虫类型 | 典型工具 | 主要特征 | 防护策略 |
|------|---------|---------|---------|---------|
| **T1** | 简单脚本 | curl, wget, Python requests | 无 JS 执行,缺失必要 Header | UA 过滤 + Header 检查 |
| **T2** | 爬虫框架 | Scrapy, Playwright (无配置), httpx | 自动处理 Cookie 但行为机械 | Header 指纹 + 速率限制 |
| **T3** | 无头浏览器 | Puppeteer, Playwright (配置过) | 执行 JS 但 Canvas/WebGL 异常 | 浏览器指纹 + 行为分析 |
| **T4** | 分布式集群 | 自建集群 + 代理池 + UA 轮换 | 跨 IP 协同,单 IP 请求少 | 关联分析 + 蜜罐 + PoW |
| **T5** | AI 代理 | LLM 控制的浏览器 / GPT 插件 | 接近真实用户但行为规律性高 | 多维指纹 + CV 分析 + 挑战 |
---
## 触发条件
用户询问防爬虫、限流、Bot 防护、爬虫识别、AI 爬虫、Scrapy 等关键词。
## 执行流程
### Phase 0威胁评估
在实施前回答以下问题,确定防护等级:
| 问题 | 回答影响 |
|------|---------|
| 保护目标API / 页面内容 / 数据资产) | 决定防护位置Nginx / Middleware / Frontend |
| 可接受的误伤率0.1% / 0.5% / 1% | 决定阈值设定的松紧 |
| 是否有 CDN/WAF | 可借用 WAF 能力,减少自研成本 |
| 业务是否允许验证码? | 影响 CAPTCHA 降级策略 |
| 需要保护登录后内容还是公开内容? | 决定是否使用前端指纹 Token |
### Phase 14前置与基础防护
1. **Phase 1 Nginx** — UA 黑名单 map、limit_req/limit_conn直接拒绝已知 Bot
2. **Phase 2 指纹** — RequestFingerprintMiddleware 检查 UA/Header/Accept/Referer输出 risk_score
3. **Phase 3 限速** — RateLimitService 分层限速 + 请求间隔变异系数分析CV+ 子网关联分析
4. **Phase 4 IP** — IpIntelligenceService 黑/白名单、TOR、数据中心、爬取广度HyperLogLog
### Phase 56高级识别
5. **Phase 5 浏览器指纹** — 前端采集 Canvas/WebGL/音频/字体/鼠标,后端校验无头特征
6. **Phase 6 AI 代理** — 请求间隔 CV、只读模式、速度、UA 与语言不匹配
### Phase 79对抗与响应
7. **Phase 7 PoW** — 工作量证明挑战,真实用户 JS 自动计算
8. **Phase 8 蜜罐** — 前端隐藏字段 + 后端蜜罐路由
9. **Phase 9 综合** — AntiScrapingMiddleware 加权评分,差异化响应(封禁 / PoW / 延迟 / 假数据)
## 验证清单
1. [ ] `curl` 请求 3 次内触发 403 或 429
2. [ ] Python `requests` 默认 UA 被 Nginx 直接拒绝
3. [ ] Scrapy 爬取 100+ 页面被封 IP
4. [ ] Puppeteer无反指纹指纹得分 ≥ 60触发挑战
5. [ ] 均匀间隔请求CV < 0.2)被 AI 行为分析识别
6. [ ] 蜜罐路由访问后 IP 被封
7. [ ] PoW 挑战前端正确求解difficulty=4约 2 秒内完成)
8. [ ] 同 /24 子网 500+ 请求触发代理池标记
9. [ ] 正常用户误触率 < 0.2%
## Tier 3 深度参考
| 文件 | 内容 |
|------|------|
| `references/implementation-phases.md` | Phase 19 完整实现代码与 Redis 监控 |
| `references/anti-scraping-patterns.md` | 反爬模式与策略速查 |

View File

@@ -0,0 +1,306 @@
# Anti-Scraping 参考模式库
## 1. 常见爬虫特征指纹
### User-Agent 黑名单(正则)
```typescript
const BOT_UA_PATTERNS = [
// 爬虫框架
/python-requests/i,
/scrapy/i,
/beautifulsoup/i,
/selenium/i,
/playwright/i,
/puppeteer/i,
/mechanize/i,
/httpclient/i,
/java\/\d/i,
/go-http-client/i,
/ruby/i,
// 命令行工具
/curl\//i,
/wget\//i,
/httpie/i,
/insomnia/i,
// 无头浏览器特征
/headlesschrome/i,
/phantomjs/i,
/slimerjs/i,
// 已知数据采集
/dataprovider/i,
/yandexbot/i,
/mj12bot/i,
/ahrefsbot/i,
/semrushbot/i,
/dotbot/i,
];
export function isBotUA(ua: string): boolean {
return BOT_UA_PATTERNS.some(p => p.test(ua));
}
```
### 允许的搜索引擎爬虫白名单
```typescript
// 合法爬虫:需要验证真实性(反向 DNS 查找)
const ALLOWED_BOTS = [
{ name: 'Googlebot', ua: /googlebot/i, rdns: 'googlebot.com' },
{ name: 'Bingbot', ua: /bingbot/i, rdns: 'search.msn.com' },
{ name: 'Baidu Spider', ua: /baiduspider/i, rdns: 'crawl.baidu.com' },
];
async function isLegitimateBot(ua: string, ip: string): Promise<boolean> {
const bot = ALLOWED_BOTS.find(b => b.ua.test(ua));
if (!bot) return false;
// 反向 DNS 验证(防止伪造 UA
const hostname = await reverseDNS(ip);
return hostname?.endsWith(bot.rdns) ?? false;
}
```
---
## 2. 风险评分算法
### 综合评分模型
```typescript
interface RiskFactors {
fingerprintScore: number; // 0-100
rateScore: number; // 0-100超速时增加
ipScore: number; // 0-100数据中心/Tor/VPN
behaviorScore: number; // 0-100行为异常
}
function calculateRiskScore(factors: RiskFactors): number {
const weights = {
fingerprint: 0.35,
rate: 0.30,
ip: 0.25,
behavior: 0.10,
};
return Math.min(
Math.round(
factors.fingerprintScore * weights.fingerprint +
factors.rateScore * weights.rate +
factors.ipScore * weights.ip +
factors.behaviorScore * weights.behavior
),
100
);
}
// 响应策略
function getResponseStrategy(score: number): 'allow' | 'slowdown' | 'challenge' | 'block' {
if (score >= 80) return 'block';
if (score >= 50) return 'challenge';
if (score >= 30) return 'slowdown';
return 'allow';
}
```
---
## 3. Redis 数据结构设计
```
# 速率限制(滑动窗口)
ZSET rl:{ip} → { timestamp: score }
ZSET rl:{ip}:{endpoint} → { timestamp: score }
# IP 黑白名单
SET ip:blocklist → { ip1, ip2, ... }
SET ip:allowlist → { ip1, ip2, ... }(合法爬虫白名单)
SET ip:tor-exit → { ip1, ip2, ... }
SET ip:datacenter → { ip1, ip2, ... }
# 蜜罐触发记录
HASH honeypot:hits → { ip: count }
# CAPTCHA 通过记录(防止重复挑战)
STRING captcha:passed:{ip} → "1"TTL 1 小时)
# 行为画像
HASH behavior:{ip} → {
first_seen: timestamp,
request_count: number,
path_entropy: number, # 访问路径多样性(低=爬虫)
referer_missing_ratio: number, # 缺少 Referer 比例(高=爬虫)
}
```
---
## 4. Nginx 层防护(可选,性能最佳)
```nginx
# /etc/nginx/conf.d/anti-scraping.conf
# 限速区域定义
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_conn_zone $binary_remote_addr zone=conn:10m;
server {
# 连接数限制(单 IP 最多 20 并发)
limit_conn conn 20;
# UA 黑名单
if ($http_user_agent ~* "(python|curl|wget|scrapy|selenium)") {
return 403;
}
# 空 UA 拒绝
if ($http_user_agent = "") {
return 403;
}
location /api/ {
limit_req zone=api burst=10 nodelay;
limit_req_status 429;
proxy_pass http://app;
}
location /api/auth/ {
limit_req zone=login burst=2 nodelay;
limit_req_status 429;
proxy_pass http://app;
}
# 蜜罐路由(真实用户不会访问)
location /admin-backup/ {
access_log /var/log/nginx/honeypot.log;
# 记录访问者 IP 并返回假数据
return 200 '{"status":"ok"}';
add_header Content-Type application/json;
}
}
```
---
## 5. Cloudflare Workers 方案Edge 层,最推荐)
```typescript
// workers/anti-scraping.ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const ip = request.headers.get('CF-Connecting-IP') ?? '';
const ua = request.headers.get('User-Agent') ?? '';
// 利用 Cloudflare 的威胁评分
const cfThreatScore = Number(request.headers.get('CF-Threat-Score') ?? 0);
if (cfThreatScore > 30) {
return new Response('Forbidden', { status: 403 });
}
// 利用 Cloudflare 的 Bot 管理分数(需开启 Bot Management
const cfBotScore = Number(request.headers.get('CF-Bot-Score') ?? 100);
if (cfBotScore < 30) {
// 低分 = 高爬虫可能性
return new Response('Forbidden', { status: 403 });
}
// 自定义限速(使用 Durable Objects 或 KV
const rateLimitKey = `rl:${ip}`;
const count = Number(await env.RATE_LIMIT.get(rateLimitKey) ?? 0);
if (count > 60) {
return new Response('Too Many Requests', {
status: 429,
headers: { 'Retry-After': '60' },
});
}
await env.RATE_LIMIT.put(rateLimitKey, String(count + 1), { expirationTtl: 60 });
return fetch(request);
},
};
```
---
## 6. 监控 DashboardDatadog / Grafana 指标)
```typescript
// lib/metrics.ts — 关键埋点
export const antiScrapingMetrics = {
// 请求被拦截
blocked: (reason: 'fingerprint' | 'rate' | 'honeypot' | 'ip' | 'captcha') => {
metrics.increment('anti_scraping.blocked', { reason });
},
// 风险评分分布
scoreDistribution: (score: number) => {
metrics.histogram('anti_scraping.risk_score', score);
},
// CAPTCHA 展示与通过
captchaImpressed: () => metrics.increment('anti_scraping.captcha.impressed'),
captchaPassed: () => metrics.increment('anti_scraping.captcha.passed'),
captchaFailed: () => metrics.increment('anti_scraping.captcha.failed'),
// 误伤监控
falsePositive: (userId: string) => {
metrics.increment('anti_scraping.false_positive');
logger.warn({ userId }, 'Possible false positive in anti-scraping');
},
};
```
---
## 7. 测试用例
```typescript
// __tests__/anti-scraping.test.ts
describe('Anti-Scraping', () => {
describe('Fingerprint Analysis', () => {
it('should flag Python requests as high risk', () => {
const score = analyzeFingerpring(mockRequest({
'user-agent': 'python-requests/2.28.0',
}));
expect(score).toBeGreaterThanOrEqual(50);
});
it('should not flag normal Chrome browser', () => {
const score = analyzeFingerpring(mockRequest({
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'accept-language': 'zh-CN,zh;q=0.9',
'accept-encoding': 'gzip, deflate, br',
'accept': 'text/html,application/xhtml+xml',
}));
expect(score).toBeLessThan(20);
});
});
describe('Rate Limiting', () => {
it('should block after exceeding limit', async () => {
const ip = '192.168.1.100';
// 发送 61 次请求
for (let i = 0; i < 61; i++) {
await checkRateLimit(ip, { windowMs: 60_000, limit: 60 });
}
const { allowed } = await checkRateLimit(ip);
expect(allowed).toBe(false);
});
});
describe('Honeypot', () => {
it('should block IP that triggers honeypot', async () => {
const ip = '10.0.0.1';
await triggerHoneypot(ip);
const isBlocked = await redis.sismember('ip:blocklist', ip);
expect(isBlocked).toBe(1);
});
});
});
```

View File

@@ -0,0 +1,100 @@
# Anti-Scraping — 完整实现代码
> 主流程与决策逻辑见 SKILL.md本文档为 Phase 19 的深度实现细节。
## Phase 1Nginx 前置拦截
```nginx
# /etc/nginx/conf.d/anti-scraping.conf
map $http_user_agent $is_bot {
default 0;
~*python-requests|python-urllib|httpx|aiohttp|scrapy|mechanize 1;
~*curl|wget|libwww-perl|Go-http-client|Java/|okhttp|axios 1;
~*HeadlessChrome|headless|PhantomJS|Playwright|Puppeteer|Selenium|webdriver 1;
~*GPTBot|ChatGPT-User|Claude-Web|PerplexityBot|anthropic-ai|CCBot|DataForSeoBot|SemrushBot 1;
~*Googlebot/|~*Bingbot/ 0;
"" 1;
}
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=page_limit:10m rate=60r/m;
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
if ($is_bot) { return 403; }
if ($http_user_agent = "") { return 444; }
location /api/ {
limit_req zone=api_limit burst=10 nodelay;
limit_conn conn_limit 20;
proxy_pass http://hyperf_upstream;
}
location /api/auth/ {
limit_req zone=login_limit burst=2 nodelay;
limit_req_status 429;
proxy_pass http://hyperf_upstream;
}
}
```
## Phase 2HTTP 请求指纹识别
`RequestFingerprintMiddleware.php`
- `BROWSER_REQUIRED_HEADERS`: accept, accept-language, accept-encoding
- `BROWSER_SECURITY_HEADERS`: sec-fetch-site, sec-fetch-mode, sec-fetch-dest, sec-ch-ua
- `SUSPICIOUS_ACCEPT_PATTERNS`: */*, application/json, text/html,*/*;q=0.9
- 评分逻辑UA 缺失 +80爬虫工具 +70无头特征 +60缺失 Header 每项 +15缺失 sec-fetch +25可疑 Accept +20API 无 Referer +15
## Phase 3速率限制与行为分析
`RateLimitService`
- `IP_RULES`: global/api/search/export/login/register 分层限速
- `analyzeRequestPattern()`: 请求间隔变异系数 CVCV < 0.3 或平均间隔 < 200ms 判定异常
- `analyzeSubnetPattern()`: /24 子网 1 分钟 > 500 请求判定代理池
- Redis key: `rl:{rule}:{ip}` (ZSET), `req_ts:{session}` (LIST)
## Phase 4IP 信誉与代理检测
`IpIntelligenceService`
- `DATACENTER_PREFIXES`: AWS/GCP/Azure/Cloudflare/DigitalOcean/Vultr 等
- `classify()`: blocklist → whitelist → tor → datacenter → residential
- `trackCrawlBreadth()`: HyperLogLog 1 小时内 > 200 唯一路径判定爬虫
## Phase 5浏览器指纹识别
前端 `collectFingerprint()`: Canvas/WebGL/音频/鼠标/字体 指纹
后端 `BrowserFingerprintService.analyze()`: SwiftShader/llvmpipe/ANGLE +30无音频 +20字体全相同 +25无鼠标 +204核0内存 +15
## Phase 6AI 代理行为识别
`AiBotDetectionService`: CV < 0.2 加 45纯只读 GET +25平均间隔 < 300ms +30UA 与语言不匹配 +15总分 ≥ 60 判定 AI Bot
## Phase 7工作量证明 PoW
前端 `solvePoW()`: SHA-256 前缀匹配difficulty=4 约 1 万次
后端 `PoWService`: generate() 生成 nonce+idverify() 校验并防重放
## Phase 8蜜罐
前端CSS 隐藏 input随机字段名提交时检测 honeypot 有值则 reportBot
后端:`/api/internal/user-export-all` 蜜罐路由,访问即封 IP 1 小时
## Phase 9综合评分与差异化响应
`AntiScrapingMiddleware`: 加权汇总 headerScore*0.2 + ipRisk*0.15 + rateScore*0.25 + breadthScore*0.15 + patternScore*0.15 + subnetScore*0.05 + fpScore*0.05
- total ≥ 90: 封 IP 2 小时
- total ≥ 70: 要求 PoW 挑战
- total ≥ 50: 延迟 0.52 秒
- total ≥ 30: 敏感接口返回空数据
## 监控与 Redis 清理
| 指标 | 告警阈值 |
|------|---------|
| 403 响应率 | > 3% |
| 429 响应率 | > 5% |
| PoW 触发量 | > 100/小时 |
| 蜜罐触发量 | > 50/天 |
| IP 封禁量 | > 200/天 |
Redis TTL: rl/req_ts/req_log/crawl_breadth/subnet_req/pow 均设 TTLip:blocklist 定期清理过期封禁。

View File

@@ -0,0 +1,71 @@
---
name: api-scaffold
version: 3.0.0
description: "生成 Hyperf API 端点脚手架Controller/Service/FormRequest/路由)。当需要新建 API 接口或 CRUD 资源时使用。含分级异常体系。"
---
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-backend-scaffold.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Hyperf API Endpoint Scaffold
## 触发条件
用户要求创建新 API 端点、路由、接口、REST 资源或 CRUD 操作。
## 执行流程
### 0. 加载规范(⚠️ 必须最先执行)
依次使用 `Read` 工具读取:
- `.cursor/rules/013-backend.mdc` — ApiResponse 格式、FormRequest、路由、认证
- `.cursor/rules/019-modular.mdc` — Controller→Service→Repository→Model 依赖方向、各层职责
### 1. 确认端点规格
| 字段 | 必填 | 默认值 |
|------|------|--------|
| 资源名称 | ✅ | — |
| HTTP 方法 | ✅ | — |
| 路由路径 | ✅ | — |
| 请求体结构 | GET 除外 | — |
| 响应结构 | ✅ | — |
| 需要认证? | ❌ | true |
| 需要权限? | ❌ | true |
| 分页? | ❌ | GET 列表自动加 |
### 2. 生成文件
生成 Controller、Service、FormRequest、Model如不存在、路由。分层约定与完整模板见 **Tier 3**
### 3. 遵循项目约定
生成前扫描:`app/Controller/` 现有模式、`AbstractController` 基类、`AppExceptionHandler`、中间件注册方式。
### 4. 异常体系
- `BusinessException` — 404/403/409/422 等可预期业务错误
- `ValidationException` — FormRequest 校验失败
- `SystemException` — 500 系统故障,记录堆栈并通知运维
异常选择决策与 `AppExceptionHandler` 实现见 **Tier 3**
## 验证
1. [ ] `php -l` 编译无错误
2. [ ] FormRequest rules 覆盖所有请求字段
3. [ ] 错误处理覆盖 400/401/403/404/422/500
4. [ ] Service 使用事务包裹写操作
5. [ ] 路由遵循 RESTful 命名复数名词、kebab-case
6. [ ] 中间件正确挂载(认证 + 权限)
7. [ ] 可预期错误使用 `BusinessException`
8. [ ] 不可预期错误使用 `SystemException`
9. [ ] `SystemException` 不向客户端暴露内部细节
10. [ ] `AppExceptionHandler` 已注册在 `config/autoload/exceptions.php`
## Tier 3 深度参考
| 文件 | 内容 |
|------|------|
| `references/code-templates.md` | Controller/Service/FormRequest/路由完整模板 |
| `references/exception-handling.md` | 分级异常体系与 AppExceptionHandler 实现 |

View File

@@ -0,0 +1,41 @@
# API 统一响应格式
## 成功响应
```json
{
"data": { ... },
"meta": {
"page": 1,
"pageSize": 20,
"total": 100,
"totalPages": 5
}
}
```
## 错误响应
```json
{
"error": "Human-readable error message",
"code": "VALIDATION_ERROR",
"details": [ ... ]
}
```
## HTTP 状态码
| 状态码 | 场景 |
|--------|------|
| 200 | 查询/更新成功 |
| 201 | 创建成功 |
| 204 | 删除成功(无响应体) |
| 400 | 请求参数验证失败 |
| 401 | 未认证 |
| 403 | 已认证但无权限 |
| 404 | 资源不存在 |
| 409 | 资源冲突(如重复创建) |
| 422 | 业务逻辑错误 |
| 429 | 请求频率超限 |
| 500 | 服务器内部错误 |

View File

@@ -0,0 +1,156 @@
# API Scaffold — 代码模板与异常体系
> 主流程见 SKILL.md本文档为 Controller/Service/FormRequest/路由 的完整代码模板。
## Hyperf 分层目录约定
| 文件 | 路径 |
|------|------|
| Controller | `app/Controller/Admin/<Resource>Controller.php` |
| Service | `app/Service/<Module>/<Resource>Service.php` |
| FormRequest | `app/Request/<Module>/Create<Resource>Request.php` |
| Model | `app/Model/<Module>/<Resource>.php`(如不存在) |
| Route | `app/Http/Admin/Router/<resource>.php` |
## Controller 模板
```php
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Request\{{Module}}\Create{{Resource}}Request;
use App\Service\{{Module}}\{{Resource}}Service;
use Hyperf\Di\Annotation\Inject;
#[Controller(prefix: '/admin/{{route-path}}')]
class {{Resource}}Controller extends AbstractController
{
#[Inject]
protected {{Resource}}Service $service;
#[RequestMapping(path: '', methods: ['GET'])]
public function list(RequestInterface $request): ResponseInterface {
$params = $request->all();
$result = $this->service->getPageList($params);
return $this->success($result);
}
#[RequestMapping(path: '{id:\d+}', methods: ['GET'])]
public function detail(int $id): ResponseInterface {
$result = $this->service->getById($id);
return $this->success($result);
}
#[RequestMapping(path: '', methods: ['POST'])]
public function create(Create{{Resource}}Request $request): ResponseInterface {
$data = $request->validated();
$result = $this->service->create($data);
return $this->success($result, 201);
}
#[RequestMapping(path: '{id:\d+}', methods: ['PUT'])]
public function update(int $id, Create{{Resource}}Request $request): ResponseInterface {
$data = $request->validated();
$result = $this->service->update($id, $data);
return $this->success($result);
}
#[RequestMapping(path: '{id:\d+}', methods: ['DELETE'])]
public function delete(int $id): ResponseInterface {
$this->service->delete($id);
return $this->success(null, message: 'Deleted');
}
}
```
## Service 模板
```php
<?php
namespace App\Service\{{Module}};
use App\Model\{{Module}}\{{Resource}};
use App\Exception\BusinessException;
use Hyperf\DbConnection\Db;
class {{Resource}}Service
{
public function getPageList(array $params): array {
$query = {{Resource}}::query();
if (!empty($params['status'])) $query->where('status', $params['status']);
$page = (int) ($params['page'] ?? 1);
$pageSize = (int) ($params['page_size'] ?? 10);
$total = $query->count();
$items = $query->orderByDesc('id')->offset(($page - 1) * $pageSize)->limit($pageSize)->get();
return ['items' => $items, 'total' => $total];
}
public function getById(int $id): {{Resource}} {
$record = {{Resource}}::find($id);
if (!$record) throw new BusinessException(404, '{{Resource}} not found');
return $record;
}
public function create(array $data): {{Resource}} {
return Db::transaction(fn () => {{Resource}}::create($data));
}
public function update(int $id, array $data): {{Resource}} {
$record = $this->getById($id);
return Db::transaction(function () use ($record, $data) {
$record->update($data);
return $record->refresh();
});
}
public function delete(int $id): void {
$record = $this->getById($id);
$record->delete();
}
}
```
## FormRequest 模板
```php
<?php
namespace App\Request\{{Module}};
use Hyperf\Validation\Request\FormRequest;
class Create{{Resource}}Request extends FormRequest
{
public function authorize(): bool { return true; }
public function rules(): array {
return [ /* Define validation rules based on resource fields */ ];
}
}
```
## 路由注册
```php
// app/Http/Admin/Router/{{resource}}.php
use App\Controller\Admin\{{Resource}}Controller;
use Hyperf\HttpServer\Router\Router;
Router::addGroup('/admin/{{route-path}}', function () {
Router::get('', [{{Resource}}Controller::class, 'list']);
Router::get('/{id:\d+}', [{{Resource}}Controller::class, 'detail']);
Router::post('', [{{Resource}}Controller::class, 'create']);
Router::put('/{id:\d+}', [{{Resource}}Controller::class, 'update']);
Router::delete('/{id:\d+}', [{{Resource}}Controller::class, 'delete']);
}, ['middleware' => [AccessTokenMiddleware::class, PermissionMiddleware::class]]);
```
## 统一响应格式
```php
protected function success(mixed $data = null, int $code = 200, string $message = 'ok'): ResponseInterface {
return $this->response->json(['code' => $code, 'message' => $message, 'data' => $data]);
}
protected function error(string $message, int $code = 500, mixed $data = null): ResponseInterface {
return $this->response->json(['code' => $code, 'message' => $message, 'data' => $data]);
}
```

View File

@@ -0,0 +1,100 @@
# API Scaffold — 分级异常体系
> 主流程见 SKILL.md本文档为异常分级与 AppExceptionHandler 完整实现。
## 异常分级
| 异常类 | 场景 | HTTP 状态码 | 日志级别 | 是否通知运维 |
|---|---|---|---|---|
| `BusinessException` | 可预期的业务错误 | 400/403/404/409/422 | warning | ❌ |
| `ValidationException` | 参数校验失败 | 422 | info | ❌ |
| `SystemException` | 不可预期的系统故障 | 500/502/503 | error | ✅ |
## BusinessException / SystemException 定义
```php
// app/Exception/BusinessException.php
class BusinessException extends ServerException
{
public function __construct(int $code = 400, string $message = 'Business error', public readonly bool $isOperational = true)
{
parent::__construct($message, $code);
}
}
// app/Exception/SystemException.php
class SystemException extends ServerException
{
public function __construct(string $message = 'System error', int $code = 500, public readonly bool $isOperational = false, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
```
## AppExceptionHandler
```php
<?php
namespace App\Exception\Handler;
use App\Exception\BusinessException;
use App\Exception\SystemException;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Hyperf\Validation\ValidationException;
class AppExceptionHandler extends ExceptionHandler
{
public function handle(\Throwable $throwable, ResponseInterface $response): ResponseInterface
{
$this->stopPropagation();
$body = match (true) {
$throwable instanceof ValidationException => ['code' => 422, 'message' => 'Validation failed', 'data' => $throwable->validator->errors()->toArray()],
$throwable instanceof BusinessException => ['code' => $throwable->getCode(), 'message' => $throwable->getMessage(), 'data' => null],
$throwable instanceof SystemException => $this->handleSystemException($throwable),
default => $this->handleUnexpected($throwable),
};
$statusCode = ($body['code'] >= 100 && $body['code'] < 600) ? $body['code'] : 500;
return $response->withStatus($statusCode)->withHeader('Content-Type', 'application/json')
->withBody(new SwooleStream(json_encode($body, JSON_UNESCAPED_UNICODE)));
}
private function handleSystemException(SystemException $e): array {
$this->logger->error('System exception', ['message' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
return ['code' => 500, 'message' => 'Internal server error', 'data' => null];
}
private function handleUnexpected(\Throwable $e): array {
$this->logger->critical('Unexpected exception', ['class' => get_class($e), 'message' => $e->getMessage()]);
return ['code' => 500, 'message' => 'Internal server error', 'data' => null];
}
public function isValid(\Throwable $throwable): bool { return true; }
}
```
## 异常选择决策
```
抛出异常?
├─ 用户输入/请求导致的?
│ ├─ 参数格式错误 → ValidationExceptionFormRequest 自动抛出)
│ ├─ 资源不存在 → BusinessException(404)
│ ├─ 无权限 → BusinessException(403)
│ └─ 业务规则冲突 → BusinessException(409/422)
└─ 系统/环境导致的?
├─ 数据库连接失败 → SystemException(503)
├─ 第三方 API 超时 → SystemException(502)
└─ 未知异常 → AppExceptionHandler 兜底
```
## Service 使用示例
```php
// Predictable: resource not found
throw new BusinessException(404, 'Order not found');
// Unpredictable: external system failure
throw new SystemException(message: 'ERP sync failed: connection timeout', previous: $e);
```

View File

@@ -0,0 +1,239 @@
---
name: bug-reproduce
version: 1.1.0
description: "结构化 Bug 复现框架,系统化定位和复现 Bug。当需要从 Bug 报告出发编写复现步骤和回归测试时使用。仅复现,不修复。"
requires: [vue-testing]
---
# Bug 复现框架PHP Hyperf + Vue 3
## 触发条件
用户提供 Bug 报告、Issue 描述,要求复现问题或编写回归测试。
## ⚠️ 核心原则
- **只复现,不修复** — 输出是失败的测试用例,不是修复代码
- **最多 3 次尝试** — 3 次无法复现即标记为 UNCONFIRMED
- **遇到硬停止条件立即放弃** — 不浪费时间在不可能的事上
## 执行流程
### 1. 信号解析Parse Signals
从用户提供的 Bug 报告中提取关键信号:
```
□ 错误消息 / 堆栈追踪
□ 复现步骤(操作序列)
□ 影响区域(后端服务 / 前端组件 / API / 数据库)
□ 环境信息PHP 版本 / 浏览器 / OS
□ 何时开始出现 / 上次正常的版本
□ 是否可稳定复现 / 偶发
```
### 2. 路由到测试策略Route to Test Layer
根据影响区域,选择对应的测试层级和工具:
| 影响区域 | 测试层 | 工具 | 关键目录 |
|---|---|---|---|
| PHP Service/Repository | 单元测试 | PHPUnit + Mockery | `Case-Database-Backend/test/Unit/` |
| Controller/API 端点 | 集成测试 | Hyperf Testing HttpClient | `Case-Database-Backend/test/Feature/` |
| 中间件/认证 | 集成测试 | Hyperf Testing | `Case-Database-Backend/test/Feature/` |
| 数据库 Migration/Model | 单元测试 | PHPUnit + SQLite | `Case-Database-Backend/test/Unit/` |
| Vue 组件逻辑 | 单元测试 | Vitest | `frontend-*/src/**/*.test.ts` |
| Vue 组件交互 | 组件测试 | Vitest + Vue Test Utils | `frontend-*/src/**/*.test.ts` |
| 跨页面流程 | E2E 测试 | Playwright | `frontend-*/e2e/` |
| API ↔ 前端联调 | E2E 测试 | Playwright | `frontend-*/e2e/` |
### 3. 定位源码Locate Source Files
```bash
# 搜索相关文件
rg -l "ClassName\|functionName" --type php --glob '!vendor/**'
rg -l "ComponentName" --glob '*.vue' --glob '!node_modules/**'
# 查看最近变更
git log --oneline -10 -- <affected-path>
# 查看 diff
git diff HEAD~5 -- <affected-path>
```
### 4. 追踪代码路径Trace Code Path
从入口点到失败点,追踪完整的调用链:
**后端路径示例**
```
Route → Middleware → Controller → FormRequest → Service → Repository → Model → DB
```
**前端路径示例**
```
用户操作 → Event Handler → Composable/Store → API Request → 响应处理 → UI 更新
```
记录每个节点的输入输出和异常处理情况。
### 5. 形成假设Hypothesize
陈述清晰、可测试的假设:
```
假设 A (70%): 当 [输入/条件] 时,代码在 [位置] 执行了 [错误行为]
因为 [根因分析]。
假设 B (20%): ...
假设 C (10%): ...
```
每个假设必须指向具体的代码行。
### 6. 查找测试模式Find Test Patterns
在编写测试前,先查找同区域的已有测试作为参考:
```bash
# 查找 PHP 测试
rg -l "class.*Test" --type php Case-Database-Backend/test/
# 查找 Vue 测试
rg -l "describe\|it\(" --glob '*.test.ts' Case-Database-Frontend-*/src/
```
使用相同的 mock 模式和 setup 结构保持一致性。
### 7. 编写失败测试Write Failing Test
测试必须满足:
- 使用步骤 6 中找到的已有模式
- 断言**正确行为**(测试在当前代码上会失败)
- 包含 Bug 引用注释
- 同时包含一个 happy path 测试(证明 setup 正确)
**PHP 测试模板**
```php
<?php
declare(strict_types=1);
namespace HyperfTest\Unit;
use PHPUnit\Framework\TestCase;
class OrderServiceTest extends TestCase
{
// Happy path — proves setup works
public function testCreateOrderWithValidData(): void
{
$service = new OrderService($this->mockRepo);
$result = $service->create(['amount' => 100]);
$this->assertNotNull($result->id);
}
// Regression — reproduces the bug
// @see https://github.com/org/repo/issues/123
public function testCreateOrderRejectsNegativeAmount(): void
{
$service = new OrderService($this->mockRepo);
$this->expectException(BusinessException::class);
$service->create(['amount' => -1]);
}
}
```
**Vitest 测试模板**
```typescript
import { getStatusText } from './OrderStatus.utils'
describe('OrderStatus (Bug #123)', () => {
// Happy path
it('should return correct status for paid order', () => {
expect(getStatusText('paid', false)).toBe('待发货')
})
// Regression — reproduces the bug
// @see https://github.com/org/repo/issues/123
it('should handle null status without crashing', () => {
expect(() => getStatusText(null, false)).not.toThrow()
expect(getStatusText(null, false)).toBe('待支付')
})
})
```
### 8. 运行并评分Run & Score
```bash
# PHP
cd Case-Database-Backend && composer test -- --filter=OrderServiceTest
# Vue
cd Case-Database-Frontend-user && npx vitest run OrderStatus.utils.test.ts
```
根据结果评定置信度:
| 置信度 | 判定条件 | 后续动作 |
|---|---|---|
| **CONFIRMED** | 测试稳定失败,失败模式与假设吻合 | 输出复现报告 |
| **LIKELY** | 测试失败,但失败方式与预期略有偏差 | 报告 + 附注偏差原因 |
| **UNCONFIRMED** | 无法触发失败 | 报告已尝试的方法 |
| **SKIPPED** | 遇到硬停止条件 | 报告停止原因 |
| **ALREADY_FIXED** | Bug 在当前代码上不可复现 | 报告何时修复的 |
### 9. 迭代或放弃Iterate or Bail
**UNCONFIRMED 后的迭代**(最多 3 次):
1. 重新审视假设 — 重读代码路径
2. 尝试不同的测试方式或层级
3. 第 3 次仍失败 → 标记 UNCONFIRMED
**硬停止条件**(遇到即立即 SKIP
- 需要真实第三方 API 凭证(支付网关、短信服务等)
- 竞态条件/时序依赖(需要精确的并发控制)
- 需要特定云基础设施AWS/阿里云特定服务)
- 需要无法脚本化的手动 UI 操作
- 需要生产数据库中的真实数据
## 输出:复现报告
```markdown
## 🐛 Bug 复现报告
**Issue**: [ID] — [标题]
**置信度**: CONFIRMED | LIKELY | UNCONFIRMED | SKIPPED | ALREADY_FIXED
### 根因分析
[1-2 句话解释 Bug 产生的机制]
### 定位
| 文件 | 行号 | 问题 |
|---|---|---|
| `path/to/file.php` | XX-YY | 描述 |
### 失败测试
`path/to/test/file` — X/Y 个测试失败:
1. `testName` — [失败描述]
### 修复提示
[修复方向的伪代码或文字描述,不写完整修复代码]
### 尝试记录
| 次数 | 假设 | 测试方法 | 结果 |
|---|---|---|---|
| 1 | [假设 A] | [测试方式] | FAIL/PASS |
| 2 | [假设 B] | [测试方式] | FAIL/PASS |
```
## 验证
1. [ ] 从 Bug 报告中提取了所有关键信号
2. [ ] 选择了正确的测试层级
3. [ ] 失败测试包含 Bug 引用注释
4. [ ] 同时有 happy path 测试证明 setup 正确
5. [ ] 置信度评分有据可依
6. [ ] 测试文件已保留(未删除)
7. [ ] 输出了结构化复现报告

View File

@@ -0,0 +1,209 @@
---
name: code-review
version: 2.0.0
description: "从六个维度系统化代码审查(安全/正确/可维护/性能/可测试/一致性)。当需要 Review 代码、检查质量或预提交检查时使用。"
---
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-code-review.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Code Review
## 触发条件
用户要求审查代码、检查 PR、评估代码质量或执行预提交检查。
## 执行流程
### 1. 确定审查范围
- 具体文件 → 审查指定文件
- 目录范围 → 审查目录下所有变更
- Git diff → `git diff HEAD~1` 审查最近变更
### 2. 六维度审查
按以下顺序逐一审查:
**🔴 安全性 (Security)**
- 硬编码密钥/凭证?
- SQL 注入 / XSS 风险?
- 未验证的用户输入?
- 不安全的认证/授权?
```php
// ❌ BAD: 直接拼接用户输入到 SQL
$users = Db::select("SELECT * FROM users WHERE name = '{$name}'");
// ✅ GOOD: 参数绑定
$users = Db::select("SELECT * FROM users WHERE name = ?", [$name]);
// ✅ GOOD: Hyperf ORM
$users = User::where('name', $name)->get();
```
```vue
<!-- BAD: 直接渲染用户输入的 HTML -->
<div v-html="userInput" />
<!-- GOOD: 文本插值自动转义 -->
<div>{{ userInput }}</div>
<!-- GOOD: 如必须用 v-html先经过 DOMPurify 清洗 -->
<div v-html="sanitize(userInput)" />
```
**🟠 正确性 (Correctness)**
- 逻辑是否正确?
- 边界条件处理?
- 错误处理完善?
- null/undefined 安全?
```php
// ❌ BAD: 无空值保护
$userName = $user->profile->name;
// ✅ GOOD: 空安全访问
$userName = $user?->profile?->name ?? 'Unknown';
```
```typescript
// ❌ BAD: 解构可能为 null 的响应
const { data } = await api.getUser(id)
// ✅ GOOD: 防御性解构
const response = await api.getUser(id)
const data = response?.data ?? null
```
**🟡 可维护性 (Maintainability)**
- 命名清晰?
- 函数长度合理(< 50 行)?
- 单一职责?
- 业务逻辑是否已提取到纯函数/composable
```vue
<!-- BAD: 组件内嵌大量业务逻辑 -->
<script setup>
const result = computed(() => {
// 30 行复杂计算逻辑...
})
</script>
<!-- GOOD: 逻辑提取到 .utils.ts -->
<script setup>
import { calculateResult } from './MyComponent.utils'
const result = computed(() => calculateResult(props.data))
</script>
```
**🔵 性能 (Performance)**
- 不必要的渲染?
- N+1 查询?
- 大数据集未分页?
- 缺少缓存机会?
```php
// ❌ BAD: N+1 查询
$orders = Order::all();
foreach ($orders as $order) {
echo $order->user->name; // each iteration queries DB
}
// ✅ GOOD: 预加载关联
$orders = Order::with('user')->get();
```
```vue
<!-- BAD: v-for 内使用复杂计算 -->
<div v-for="item in list" :key="item.id">
{{ heavyCompute(item) }}
</div>
<!-- GOOD: 预计算或使用 computed -->
<div v-for="item in computedList" :key="item.id">
{{ item.computedValue }}
</div>
```
**🟣 可测试性 (Testability)**
- 纯函数是否已提取到 `.utils.ts`
- composable 是否可独立测试?
- 关键路径是否有 `data-testid`
- 是否有过度耦合使测试困难?
```typescript
// ❌ BAD: 逻辑耦合在组件中,无法独立测试
// 必须 mount 整个组件才能测试 formatDate
// ✅ GOOD: 提取为可独立测试的纯函数
// date.utils.ts
export function formatDate(date, format = 'YYYY-MM-DD') { ... }
// date.utils.test.ts
it('should format date correctly', () => { ... })
```
**⚪ 一致性 (Consistency)**
- 命名风格统一?
- 遵循项目模式?
- import 顺序统一?
- 错误处理模式统一?
### 3. 预提交检查联动
审查代码时,同步执行以下自动化检查:
```bash
# PHP 后端
cd Case-Database-Backend
vendor/bin/phpstan analyse --level=5 --no-progress # 静态分析
vendor/bin/php-cs-fixer fix --dry-run --diff # 代码格式
# Vue 前端
cd Case-Database-Frontend-user
npx eslint --quiet src/ # ESLint
npx vitest run --reporter=verbose # 单元测试
cd ..
```
如果项目配置了 husky + lint-staged确认 `.husky/pre-commit` 钩子覆盖:
- [ ] ESLint (前端)
- [ ] PHPStan (后端)
- [ ] 代码格式检查 (Prettier / PHP CS Fixer)
### 4. 输出格式
```markdown
## 代码审查报告
**范围**: <文件/目录/PR>
**总体评价**: ✅ 建议合并 | ⚠️ 需修改后合并 | ❌ 需重写
### 自动化检查
- ESLint: ✅ 通过 | ❌ N 个错误
- PHPStan: ✅ 通过 | ❌ N 个错误
- 测试: ✅ 通过 | ❌ N 个失败
### 发现
#### 🔴 Must Fix
1. **[SEC]** `file.ts:23` — 硬编码 API Key
→ 修复:移入环境变量
#### 🟡 Should Fix
2. **[MAINT]** `service.ts:45` — 函数过长87 行)
→ 建议:拆分为 3 个私有方法
3. **[TEST]** `OrderForm.vue` — 价格计算逻辑未提取到 .utils.ts
→ 建议:提取为纯函数并添加单元测试
#### 🟢 Suggestion
4. **[PERF]** `list.vue:12` — 列表项未使用 `v-memo`
→ 建议:添加 `v-memo="[item.id]"` 减少重渲染
```
## 验证
1. [ ] 六个维度全部覆盖(含可测试性)
2. [ ] 每个发现有具体文件和行号
3. [ ] 每个发现有修复建议(含 BAD/GOOD 代码对比)
4. [ ] 发现按严重程度分级
5. [ ] 预提交检查已执行ESLint + PHPStan + 测试)

View File

@@ -0,0 +1,197 @@
---
name: component-scaffold
version: 4.1.0
description: "生成 Vue 3 SFC 组件脚手架,含单元测试和类型安全 Props。当需要新建组件、拆分子组件或创建复合组件时使用。管理端用 Element Plus用户端用 Headless UI。"
requires: [vue-testing]
---
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-component-scaffold.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Vue 3 Component Scaffold
> **⚠️ 前端识别**:生成组件前必须确认目标前端。
> - **管理端** (`Case-Database-Frontend-admin/`):使用 Element Plus + Tailwind
> - **用户端** (`Case-Database-Frontend-user/`):使用 Headless UI + Tailwind**禁止 Element Plus**
## 触发条件
用户要求创建新的 Vue 组件、页面组件、UI 元素或交互模块。
## 执行流程
### 0. 加载规范(⚠️ 必须最先执行)
依次读取 `.cursor/rules/010-typescript.mdc``.cursor/rules/011-vue.mdc``.cursor/rules/019-modular.mdc`,提取类型注解要求(隐式 any 禁令、ref 泛型规范、Composable 类型规范、script setup、defineProps/Emits、组件分类、拆分阈值、Composable 提取规则。
> **Tier 3**Vue 3 完整 API 见 `references/vue-api-reference.md`。
### 0.5 ⚠️ 生成前强制结构规划(禁止跳过)
**写代码前必须先输出文件结构和组件职责说明。**
#### A. 检查是否已有可复用的基础组件
在生成新组件前,先扫描以下路径,**避免重复造轮子**
- `src/components/core/` — 通用基础组件(按钮、输入框、卡片等)
- `src/components/custom/` — 业务定制组件
若已有 `FormInput.vue``BaseCard.vue` 等,**直接复用,不重新生成**。
#### B. 用户端表单输入:优先复用 FormInput 模式
用户端页面中表单输入框若出现 ≥3 个,必须使用 `FormInput` 基础组件:
```vue
<!-- src/components/core/FormInput/FormInput.vue -->
<script setup>
defineProps({
modelValue: { type: String, default: '' },
type: { type: String, default: 'text' },
placeholder: { type: String, default: '' },
icon: { type: Object, default: null }, // Lucide 图标组件
disabled: { type: Boolean, default: false },
error: { type: String, default: '' },
})
defineEmits(['update:modelValue'])
</script>
<template>
<div class="relative">
<component :is="icon" v-if="icon"
class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500 w-5 h-5" />
<input
:type="type"
:value="modelValue"
:placeholder="placeholder"
:disabled="disabled"
@input="$emit('update:modelValue', $event.target.value)"
:class="[
'w-full border-b py-3 outline-none transition-colors',
icon ? 'pl-10 pr-4' : 'px-4',
'bg-transparent border-gray-300 dark:border-[#333333]',
'text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-600',
'focus:border-[#C41E3A] dark:focus:border-[#C41E3A]',
error ? 'border-red-500' : '',
]"
data-testid="form-input"
/>
<p v-if="error" class="mt-1 text-xs text-red-500">{{ error }}</p>
</div>
</template>
```
> 若项目中尚无 `FormInput`,在生成使用它的页面前先生成该核心组件。
#### C. 重复 UI 模式检测
检查需求中是否存在重复结构:
| 场景 | 检测标准 | 操作 |
|------|---------|------|
| 表单输入项 | ≥3 个相同结构的 input 组 | 提取 `FormInput.vue` |
| 内容卡片 | ≥3 个相同布局的卡片 | 提取 `CaseCard.vue` / `DesignerCard.vue` |
| 操作按钮组 | ≥2 处相同按钮组合 | 提取 `ActionGroup.vue` |
| 阶段/Tab 面板 | ≥2 个结构相同的面板 | 提取 `StagePanel.vue` |
#### D. 输出组件文件结构(代码前必须输出)
```
# 本次将生成以下文件:
src/components/<type>/<ComponentName>/
├── <ComponentName>.vue ← 主组件(目标 ≤ 120 行)
├── <ComponentName>.test.ts ← 单元测试
└── index.ts ← barrel export
# 依赖(已存在 / 需新建):
- FormInput.vue: [已存在 / 需新建]
- BaseCard.vue: [已存在 / 不需要]
```
---
### 1. 确认组件规格
| 字段 | 必填 | 默认值 |
|------|------|--------|
| 组件名 | ✅ | — |
| 组件类型 | ❌ | UI 组件 |
| 所在目录 | ❌ | `src/components/` |
| Props / Emits | ❌ | 根据需求推断 |
类型与目录:`core``src/components/core/``custom``custom/``layout``layouts/``page``views/<module>/components/``form``custom/`
### 2. 扫描项目模式
读取 `src/components/` 确认 Props 声明、样式方案、命名前缀约定、已有的 core 组件列表。
### 3. 生成文件结构
按步骤 0.5 输出的结构逐文件生成,**禁止将多个组件合并到一个文件**。
```
src/components/<type>/<ComponentName>/
├── <ComponentName>.vue
├── <ComponentName>.test.ts
└── index.ts
```
### 4. 组件模板
根据类型选择:基础 UI / 表格 / 表单对话框 / 复合组件。完整模板与设计决策树见 **Tier 3**
**单组件行数限制**
- template 区域 ≤ 80 行
- script 区域 ≤ 60 行(超出提取 composable
- 整个 SFC ≤ 150 行(超出必须拆分子组件)
### 4.5 设计决策树(必须执行)
- ≥3 布尔 prop→ 显式变体组件
- 多子组件共享状态?→ provide/inject
- script 逻辑 > 60 行?→ 提取 `use<ComponentName>.ts` composable**必须遵循 `010-typescript.mdc` 的 Composable 类型规范**:参数有类型、`ref` 有泛型、业务类型用 `import type`
- >2 处复用?→ `src/components/` + slot
- 同一 UI 结构重复 ≥3 次?→ 提取基础组件(见步骤 0.5C
- 否则 → 基础 UI 模板
### 5. 测试与 Barrel export
测试至少包含渲染测试。`index.ts` 导出:`export { default as ComponentName } from './ComponentName.vue'`。详细测试见 `vue-testing` 技能。
## 验证
1. [ ] ESLint 无报错
2. [ ] Props 使用对象语法 `defineProps({ key: { type, default } })`**若模板可直接访问 props 字段则不保存返回值**(避免 `unused-vars` 报错;仅当 script 中需要访问 `props.xxx` 时才保存:`const props = defineProps(...)`
3. [ ] Emits 使用数组语法,事件名使用 camelCase非 kebab-case
4. [ ] 包含 `data-testid`
5. [ ] 测试至少一个渲染测试
6. [ ] 管理端Element Plus 用于组件Tailwind 用于布局用户端Headless UI + Tailwind禁止 Element Plus
7. [ ] barrel export 正确
8. [ ] **单 SFC ≤ 150 行**
9. [ ] **重复 UI 结构≥3 次)已提取为基础组件**
10. [ ] **表单输入 ≥3 个已复用 FormInput.vue**(用户端)
11. [ ] **子组件模板中无 `v-model` 直接绑定 prop 嵌套属性**(参见 `011-vue.mdc` Props 数据流模式)
12. [ ] **提取的 composableuse*.ts参数有类型注解、`ref([])` / `ref(null)` 有泛型标注**(参见 `010-typescript.mdc` Composable 类型规范)
### Red Flags触发则停止并重构
- ❌ 未输出文件结构直接写代码 → 停止,先输出步骤 0.5D 的结构
- ❌ 单 SFC > 150 行 → 拆分子组件
- ❌ 相同 Tailwind 输入框结构写了 ≥3 次 → 提取 FormInput.vue
- ❌ Options API → 改用 `<script setup>`
-`const props = defineProps(...)` 但 script 中从不访问 `props.xxx` → 去掉 `const props =`,避免 `unused-vars` lint 报错
-`catch (e) { ... }` 但 catch 块中不使用 `e` → 改为 `catch { ... }`(无参 catch避免 `unused-vars` 报错
- ❌ 直接修改 props → 通过 emit含隐蔽变体`v-model="prop.field"``v-model="prop[key]"`、事件回调中 `prop[k] = v`
- ❌ watch deep:true 滥用 → 精准监听
- ❌ script 逻辑 > 60 行未提取 → 拆分为 use*.ts
- ❌ composable 参数无类型 / `ref([])` 无泛型 → 补全类型注解(`strict: true` 下必报错)
- ❌ ≥3 布尔 prop 控制渲染 → 拆分变体
- ❌ prop drilling 传递同状态 → 改用 provide/inject
## Tier 3 深度参考
| 文件 | 内容 |
|------|------|
| `references/component-templates.md` | 基础/表格/表单/复合组件模板、决策树、Prop 反模式、错误处理、UI 反模式 |
| `references/vue-api-reference.md` | Vue 3 完整 API 索引 |
| `references/naming-conventions.md` | 组件命名规范 |

View File

@@ -0,0 +1,117 @@
# Component Scaffold — 组件模板
> 主流程见 SKILL.md本文档为各类 Vue 3 组件的完整模板。
>
> **⚠️ 双前端区分**:本文件中使用 `el-*` 组件的模板**仅适用于管理端** (`Case-Database-Frontend-admin/`)。
> 用户端 (`Case-Database-Frontend-user/`) 使用 Headless UI + Tailwind CSS**禁止引入 Element Plus**。
## 基础 UI 组件
```vue
<script setup>
const props = defineProps({
title: { type: String, required: true },
loading: { type: Boolean, default: false },
})
const emit = defineEmits(['refresh'])
</script>
<template>
<div class="rounded-lg border border-gray-200 p-4" data-testid="component-name">
<h3 class="text-lg font-semibold mb-2">{{ props.title }}</h3>
<slot />
<el-button v-if="props.loading" :loading="true" class="mt-2" />
</div>
</template>
```
## 表格组件 (Element Plus)
```vue
<script setup>
import { useTable } from '@/hooks/useTable'
const props = defineProps({ apiUrl: { type: String, required: true }, columns: { type: Array, required: true } })
const { loading, dataList, pagination, loadData } = useTable((params) => request.get(props.apiUrl, { params }))
onMounted(() => loadData())
</script>
<template>
<div class="space-y-4">
<el-table v-loading="loading" :data="dataList" border stripe data-testid="data-table">
<el-table-column v-for="col in columns" :key="col.prop" v-bind="col" />
<el-table-column label="操作" fixed="right" width="180">
<template #default="{ row }"><slot name="actions" :row="row" /></template>
</el-table-column>
</el-table>
<el-pagination v-model:current-page="pagination.current" v-model:page-size="pagination.size" :total="pagination.total"
layout="total, sizes, prev, pager, next" @current-change="loadData" @size-change="loadData" />
</div>
</template>
```
## 表单对话框组件
```vue
<script setup>
const props = defineProps({ visible: { type: Boolean, required: true }, title: { type: String, required: true }, formData: { type: Object, default: () => ({}) } })
const emit = defineEmits(['update:visible', 'submit'])
const formRef = ref()
const form = reactive({ ...props.formData })
const rules = { name: [{ required: true, message: '请输入名称', trigger: 'blur' }] }
async function handleSubmit() { await formRef.value?.validate(); emit('submit', { ...form }) }
</script>
<template>
<el-dialog :model-value="visible" :title="title" width="600px" @update:model-value="emit('update:visible', $event)">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="名称" prop="name"><el-input v-model="form.name" placeholder="请输入" /></el-form-item>
<slot name="form-fields" :form="form" />
</el-form>
<template #footer>
<el-button @click="emit('update:visible', false)">取消</el-button>
<el-button type="primary" @click="handleSubmit">确认</el-button>
</template>
</el-dialog>
</template>
```
## 复合组件 (Compound + provide/inject)
Provider 组件provide 状态 + actions。子组件inject 消费。keys.ts 定义 Symbol + useDataPanel()。消费者组合:`<DataPanelProvider :fetch-fn="orderApi.list"><DataPanelContent /></DataPanelProvider>`
## 组件设计决策树
```
≥3 个布尔 prop 控制渲染?→ 拆分为显式变体组件
多个子组件共享状态?→ provide/inject 复合组件
业务逻辑 > 50 行?→ 提取 composable
>2 处复用?→ 放入 src/components/,使用 slot
否则 → 基础 UI 组件模板
```
## 逻辑提取规则 (CRITICAL)
纯转换逻辑必须提取到 `ComponentName.utils.ts`,组件只保留 UI 和事件。文件结构:`ComponentName.vue` + `ComponentName.utils.ts` + `ComponentName.utils.test.ts` + `ComponentName.test.ts`(可选)+ `index.ts`
## 复杂度阈值
| 指标 | 阈值 | 动作 |
|---|---|---|
| 组件总行数 | > 200 | ⚠️ 考虑拆分 |
| 组件总行数 | > 400 | 🔴 必须拆分 |
| script 逻辑行数 | > 50 | 提取 composable |
| 布尔 Props | ≥ 3 | 拆分为变体组件 |
## Prop 反模式与显式变体
❌ 4 个布尔 = 16 种状态v-if 地狱。✅ 显式变体:`AdminEditPanel` / `PublicReadonlyPanel` / `DraftPreviewPanel`,通过 slot 复用共享子组件。Slot vs Prop简单数据用 Prop自定义渲染用 Slot控制显示用有无 slot 内容。
## 错误处理规范
| 场景 | 组件 |
|------|------|
| 操作成功/失败即时反馈 | ElMessage |
| 需用户确认 | ElMessageBox |
| 异步持续状态 | ElNotification |
| 页面级加载失败 | 内联错误 UI + 重试按钮 |
## UI 设计反模式
居中一切、紫色渐变、大圆角、过度阴影、空白页大插图、过多动画 → 以 Element Plus 为基准,优先功能清晰度。

View File

@@ -0,0 +1,32 @@
# 组件命名规范
## 文件命名
| 类型 | 命名 | 示例 |
|------|------|------|
| 组件文件 | PascalCase.vue | `UserProfile.vue` |
| 测试文件 | PascalCase.test.ts | `UserProfile.test.ts` |
| 样式文件 | 内联 `<style scoped>` | `UserProfile.vue` |
| Composable | use + PascalCase.ts | `useUserProfile.ts` |
| 工具函数 | camelCase.ts | `formatDate.ts` |
## Props 接口
```typescript
// ✅ Good: use JSDoc typedef for props-like structure
/**
* @typedef {Object} UserProfileProps
*/
// ❌ Bad: ambiguous generic name
/** @typedef {Object} Props */
```
## 组件类型
| 类型 | 目录 | 特征 |
|------|------|------|
| UI 组件 | `components/ui/` | 无业务逻辑,纯展示 |
| Feature 组件 | `components/features/` | 包含业务逻辑 |
| Layout 组件 | `components/layout/` | 页面布局 |
| Form 组件 | `components/forms/` | 表单及验证 |

View File

@@ -0,0 +1,481 @@
# Vue.ts 3 API Reference
Quick reference with links to official Vue.ts documentation.
## Official Documentation
**Main Site:** https://vuejs.org
## Table of Contents
1. [Getting Started](#getting-started)
2. [Essentials](#essentials)
3. [Components In-Depth](#components-in-depth)
4. [Reusability](#reusability)
5. [Built-in Components](#built-in-components)
6. [API Reference](#api-reference)
7. [TypeScript Patterns](#typescript-patterns)
---
## Getting Started
### Introduction
**URL:** https://vuejs.org/guide/introduction.html
- What is Vue?
- Progressive Framework
- Single-File Components
- API Styles (Options vs Composition)
- Which to choose?
### Quick Start
**URL:** https://vuejs.org/guide/quick-start.html
- Creating a Vue Application
- Using Vue from CDN
- With build tools (Vite)
- IDE Setup
### Creating an Application
**URL:** https://vuejs.org/guide/essentials/application.html
- Application instance
- Root component
- Mounting the app
- App configurations
---
## Essentials
### Template Syntax
**URL:** https://vuejs.org/guide/essentials/template-syntax.html
- Text interpolation
- Raw HTML (v-html)
- Attribute bindings (v-bind)
- TypeScript expressions
- Directives
### Reactivity Fundamentals
**URL:** https://vuejs.org/guide/essentials/reactivity-fundamentals.html
- `ref()` - Reactive state for primitives
- `reactive()` - Reactive objects
- Reactive proxy vs original
- `nextTick()` - DOM update timing
### Computed Properties
**URL:** https://vuejs.org/guide/essentials/computed.html
- Basic usage
- Computed caching vs methods
- Writable computed
- Best practices
### Class and Style Bindings
**URL:** https://vuejs.org/guide/essentials/class-and-style.html
- Binding HTML classes
- Binding inline styles
- Object syntax
- Array syntax
### Conditional Rendering
**URL:** https://vuejs.org/guide/essentials/conditional.html
- `v-if`, `v-else-if`, `v-else`
- `v-show`
- v-if vs v-show
- v-if with v-for
### List Rendering
**URL:** https://vuejs.org/guide/essentials/list.html
- `v-for` with arrays
- `v-for` with objects
- `v-for` with ranges
- Maintaining state with `key`
- Array change detection
### Event Handling
**URL:** https://vuejs.org/guide/essentials/event-handling.html
- Listening to events (`v-on` / `@`)
- Method handlers
- Inline handlers
- Event modifiers (.stop, .prevent, etc.)
- Key modifiers
### Form Input Bindings
**URL:** https://vuejs.org/guide/essentials/forms.html
- `v-model` basics
- Text, textarea inputs
- Checkboxes, radio buttons
- Select dropdowns
- Modifiers (.lazy, .number, .trim)
### Lifecycle Hooks
**URL:** https://vuejs.org/guide/essentials/lifecycle.html
- Lifecycle diagram
- `onMounted()`, `onUpdated()`, `onUnmounted()`
- `onBeforeMount()`, `onBeforeUpdate()`, `onBeforeUnmount()`
- `onErrorCaptured()`, `onActivated()`, `onDeactivated()`
### Watchers
**URL:** https://vuejs.org/guide/essentials/watchers.html
- `watch()` - Watch specific sources
- `watchEffect()` - Auto-track dependencies
- Deep watchers
- Eager watchers (immediate)
- Callback flush timing
- Stopping watchers
### Template Refs
**URL:** https://vuejs.org/guide/essentials/template-refs.html
- Accessing DOM elements
- Refs inside v-for
- Function refs
- Component refs
### Components Basics
**URL:** https://vuejs.org/guide/essentials/component-basics.html
- Defining components
- Using components
- Passing props
- Listening to events
- Slots
---
## Components In-Depth
### Registration
**URL:** https://vuejs.org/guide/components/registration.html
- Global registration
- Local registration
- Component name casing
### Props
**URL:** https://vuejs.org/guide/components/props.html
- Props declaration
- Prop types and validation
- Prop passing details
- One-way data flow
- Boolean casting
- Prop validation
### Events
**URL:** https://vuejs.org/guide/components/events.html
- Emitting and listening to events
- Event arguments
- Declaring emitted events
- Events validation
- Usage with v-model
### Component v-model
**URL:** https://vuejs.org/guide/components/v-model.html
- Basic usage
- v-model arguments
- Multiple v-model bindings
- Custom modifiers
### Fallthrough Attributes
**URL:** https://vuejs.org/guide/components/attrs.html
- Attribute inheritance
- Disabling inheritance
- Accessing fallthrough attributes
- Multi-root nodes
### Slots
**URL:** https://vuejs.org/guide/components/slots.html
- Default slot content
- Named slots
- Scoped slots
- Renderless components
### Provide / Inject
**URL:** https://vuejs.org/guide/components/provide-inject.html
- Basic usage
- App-level provide
- Working with reactivity
- Working with Symbol keys
### Async Components
**URL:** https://vuejs.org/guide/components/async.html
- Basic usage
- Loading and error states
- Using with Suspense
---
## Reusability
### Composables
**URL:** https://vuejs.org/guide/reusability/composables.html
- What is a composable?
- Mouse tracker example
- Async state example
- Conventions and best practices
- Usage restrictions
- Extracting composables
### Custom Directives
**URL:** https://vuejs.org/guide/reusability/custom-directives.html
- Introduction
- Directive hooks
- Hook arguments
- Function shorthand
- Object literals
- Usage on components
### Plugins
**URL:** https://vuejs.org/guide/reusability/plugins.html
- Introduction
- Writing a plugin
- Plugin options
- Provide / inject with plugins
---
## Built-in Components
### Transition
**URL:** https://vuejs.org/guide/built-ins/transition.html
- Basic usage
- CSS-based transitions
- TypeScript hooks
- Reusable transitions
- Appear on initial render
- Transition between elements
- Transition modes
### TransitionGroup
**URL:** https://vuejs.org/guide/built-ins/transition-group.html
- Basic usage
- Move transitions
- Staggering list transitions
### KeepAlive
**URL:** https://vuejs.org/guide/built-ins/keep-alive.html
- Basic usage
- Include / exclude
- Max cached instances
- Lifecycle of cached instance
### Teleport
**URL:** https://vuejs.org/guide/built-ins/teleport.html
- Basic usage
- Using with components
- Multiple teleports on same target
- Disabling teleport
### Suspense
**URL:** https://vuejs.org/guide/built-ins/suspense.html
- Async dependencies
- Loading state
- Error handling
- Combining with Transitions
- **Note:** Experimental feature
---
## API Reference
### Global API
**URL:** https://vuejs.org/api/application.html
- Application API
- General API
### Composition API - Setup
**URL:** https://vuejs.org/api/composition-api-setup.html
- `setup()` function
- `<script setup>` syntax
### Composition API - Reactivity Core
**URL:** https://vuejs.org/api/reactivity-core.html
- `ref()`, `computed()`, `reactive()`, `readonly()`
- `watchEffect()`, `watchPostEffect()`, `watchSyncEffect()`
- `watch()`
- `isRef()`, `unref()`, `toRef()`, `toRefs()`, `toValue()`
- `isProxy()`, `isReactive()`, `isReadonly()`
### Composition API - Reactivity Utilities
**URL:** https://vuejs.org/api/reactivity-utilities.html
- `isRef()`, `unref()`, `toRef()`, `toRefs()`, `toValue()`
- `isProxy()`, `isReactive()`, `isReadonly()`
- `shallowRef()`, `triggerRef()`, `customRef()`
- `shallowReactive()`, `shallowReadonly()`
### Composition API - Reactivity Advanced
**URL:** https://vuejs.org/api/reactivity-advanced.html
- `shallowRef()`, `triggerRef()`, `customRef()`
- `shallowReactive()`, `shallowReadonly()`
- `toRaw()`, `markRaw()`
- `effectScope()`, `getCurrentScope()`, `onScopeDispose()`
### Composition API - Lifecycle Hooks
**URL:** https://vuejs.org/api/composition-api-lifecycle.html
- `onMounted()`, `onUpdated()`, `onUnmounted()`
- `onBeforeMount()`, `onBeforeUpdate()`, `onBeforeUnmount()`
- `onErrorCaptured()`, `onRenderTracked()`, `onRenderTriggered()`
- `onActivated()`, `onDeactivated()`, `onServerPrefetch()`
### Composition API - Dependency Injection
**URL:** https://vuejs.org/api/composition-api-dependency-injection.html
- `provide()`, `inject()`
- `hasInjectionContext()`
### Composition API - Helpers
**URL:** https://vuejs.org/api/composition-api-helpers.html
- `useAttrs()`, `useSlots()`
- `useModel()`, `useTemplateRef()`
- `useId()`, `useCssModule()`
### Options API - State
**URL:** https://vuejs.org/api/options-state.html
- `data`, `props`, `computed`
- `methods`, `watch`, `emits`
- `expose`
### Options API - Rendering
**URL:** https://vuejs.org/api/options-rendering.html
- `template`, `render`
- `compilerOptions`
### Options API - Lifecycle
**URL:** https://vuejs.org/api/options-lifecycle.html
- `beforeCreate`, `created`
- `beforeMount`, `mounted`
- `beforeUpdate`, `updated`
- `beforeUnmount`, `unmounted`
- `errorCaptured`, `renderTracked`, `renderTriggered`
- `activated`, `deactivated`, `serverPrefetch`
### Options API - Composition
**URL:** https://vuejs.org/api/options-composition.html
- `provide`, `inject`
- `mixins`, `extends`
### Options API - Misc
**URL:** https://vuejs.org/api/options-misc.html
- `name`, `inheritAttrs`, `components`, `directives`
### Component Instance
**URL:** https://vuejs.org/api/component-instance.html
- `$data`, `$props`, `$el`, `$refs`
- `$parent`, `$root`, `$slots`, `$attrs`
- `$watch()`, `$emit()`, `$forceUpdate()`, `$nextTick()`
### Built-in Directives
**URL:** https://vuejs.org/api/built-in-directives.html
- `v-text`, `v-html`, `v-show`, `v-if`, `v-else`, `v-else-if`, `v-for`
- `v-on`, `v-bind`, `v-model`
- `v-slot`, `v-pre`, `v-once`, `v-memo`, `v-cloak`
### Built-in Components
**URL:** https://vuejs.org/api/built-in-components.html
- `<Transition>`, `<TransitionGroup>`
- `<KeepAlive>`, `<Teleport>`, `<Suspense>`
### Built-in Special Elements
**URL:** https://vuejs.org/api/built-in-special-elements.html
- `<component>`, `<slot>`
### Built-in Special Attributes
**URL:** https://vuejs.org/api/built-in-special-attributes.html
- `key`, `ref`, `is`
### Single-File Component Syntax
**URL:** https://vuejs.org/api/sfc-syntax.html
- Language blocks
- Automatic name inference
- Pre-processors
- Src imports
- Custom blocks
### SFC `<script setup>`
**URL:** https://vuejs.org/api/sfc-script-setup.html
- Basic syntax
- Top-level bindings
- Using components
- Using custom directives
- defineProps(), defineEmits()
- defineExpose(), defineOptions(), defineSlots(), defineModel()
- useSlots(), useAttrs()
- Normal `<script>` alongside `<script setup>`
### SFC CSS Features
**URL:** https://vuejs.org/api/sfc-css-features.html
- Scoped CSS
- CSS modules
- `v-bind()` in CSS
- `<style>` with `src` imports
---
## TypeScript Patterns
### TypeScript with Composition API
**URL:** https://vuejs.org/guide/extras/composition-api-faq.html
- Define props with runtime object syntax
- Define emits with array/object syntax
- Use JSDoc for complex shapes
- Keep composables in `use*.ts`
- Prefer clear runtime validation for public component APIs
### TypeScript with Options API
**URL:** https://vuejs.org/guide/essentials/component-basics.html
- Use `props` runtime validation
- Keep event names explicit in `emits`
- Avoid implicit global properties where possible
- Prefer composition API for new code
### Overview
**URL:** https://vuejs.org/guide/scaling-up/tooling.html
- IDE support
- Linting and formatting integration
- Volar extension basics
- Build-tool and plugin usage notes
---
## Additional Resources
### Best Practices
**URL:** https://vuejs.org/guide/best-practices/production-deployment.html
- Production deployment
- Performance
- Accessibility
- Security
### Scaling Up
**URL:** https://vuejs.org/guide/scaling-up/sfc.html
- Single-File Components
- Tooling
- Routing
- State management
- Testing
- Server-Side Rendering (SSR)
### Style Guide
**URL:** https://vuejs.org/style-guide/
- Priority A: Essential (Error Prevention)
- Priority B: Strongly Recommended
- Priority C: Recommended
- Priority D: Use with Caution
### Vue Router
**URL:** https://router.vuejs.org/
- Official routing library
### Pinia
**URL:** https://pinia.vuejs.org/
- Official state management library
### Vite
**URL:** https://vitejs.dev/
- Recommended build tool
---
## Quick Search Tips
When searching official docs:
1. Use the search bar at https://vuejs.org
2. For API details, go directly to https://vuejs.org/api/
3. For guides and concepts, start at https://vuejs.org/guide/
4. For examples, check https://vuejs.org/examples/
All documentation is available in multiple languages using the language selector.

View File

@@ -0,0 +1,87 @@
---
name: database-migration
version: 3.0.0
description: "使用 Hyperf Migrations 安全管理数据库 Schema 变更。当需要创建表、添加字段或修改索引时使用。确保迁移安全可回滚。"
---
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-database-migration.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Hyperf Database Migration
## ⚠️ 安全等级ORANGE — 执行前必须确认
## 迁移核心原则
1. **每次变更都是迁移** — 禁止手动 DDL
2. **Schema 与 Data 严格分离** — DDL 和 DML 分文件
3. **迁移部署后不可变** — 已执行迁移禁止修改
4. **生产前向原则** — 回滚用新前向迁移修正
5. **新字段安全** — 新增字段必须 nullable 或有默认值
6. **迁移前测试** — 大表先在副本验证
## 目录约定
```
Case-Database-Backend/
├── database/
│ ├── migrations/ ← 迁移文件(已通过 DI 工厂注册到 Migrator
│ └── seeders/ ← 种子文件(已通过 DI 工厂注册到 Seed
```
路径配置说明:
- Hyperf 默认迁移路径为 `migrations/`,本项目通过 `App\Database\MigratorFactory` 覆盖为 `database/migrations/`
- Seeder 路径通过 `App\Database\SeedFactory` 覆盖为 `database/seeders/`
- `gen:migration` / `gen:seeder` 路径在 `config/autoload/devtool.php` 中配置
## 触发条件
用户要求修改数据库结构、添加/删除字段、创建/删除表、修改索引或关联。
## 执行流程
### 0. 加载规范
读取 `.cursor/rules/014-database.mdc`提取表设计、索引规则、Migration 写法、高并发注意事项。
### 1. 理解变更需求
确认:变更什么、是否涉及数据迁移、是否可逆、是否有线上数据、数据量级、环境阶段。
### 2. 生成迁移
```bash
php bin/hyperf.php gen:migration create_{{table_name}}_table
# 文件自动生成到 database/migrations/ 目录
# 命名: create_orders_table | add_status_to_orders | remove_legacy_field_from_users
```
### 3. 编写迁移
Schema::create / Schema::table含 id、audit 字段、索引。down() 必须完整可逆。幂等、Expand-Contract、批量迁移见 **Tier 3**
### 4. 高并发表设计检查
主键 BIGINT UNSIGNED、utf8mb4、金额 DECIMAL、状态 VARCHAR/TINYINT、外键索引、常用 WHERE 索引、复合索引最左前缀、单表索引 ≤6、JSON 仅非查询、避免过多 TEXT。
### 5. 执行与更新
`php bin/hyperf.php migrate``migrate:status``migrate:rollback``gen:model {{table_name}}`。更新 Model、Service、Repository、`docs/architecture/data-model.md`
## 验证
1. [ ] `migrate` 无错误
2. [ ] `migrate:rollback` 可回滚(开发)
3. [ ] Model `$fillable` / `$casts` 正确
4. [ ] 外键有索引
5. [ ] 新字段 nullable 或有默认值
6. [ ] Schema 与 Data 迁移分文件
7. [ ] 幂等检查通过
8. [ ] SQL 人工审查
9. [ ] data-model.md 已更新
## Tier 3 深度参考
| 文件 | 内容 |
|------|------|
| `references/migration-patterns.md` | 幂等、Expand-Contract、批量迁移、大表策略、危险操作 |

View File

@@ -0,0 +1,73 @@
# Database Migration — 迁移模式与示例
> 主流程见 SKILL.md本文档为幂等迁移、Expand-Contract、批量迁移、大表策略的完整实现。
## 幂等迁移
```php
public function up(): void
{
if (!Schema::hasTable('production_orders')) {
Schema::create('production_orders', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('order_no', 50)->unique();
$table->timestamps();
$table->softDeletes();
});
}
if (!Schema::hasColumn('production_orders', 'priority')) {
Schema::table('production_orders', function (Blueprint $table) {
$table->tinyInteger('priority')->default(0)->after('status');
});
}
if (!$this->hasIndex('production_orders', 'idx_orders_priority')) {
Schema::table('production_orders', function (Blueprint $table) {
$table->index('priority', 'idx_orders_priority');
});
}
}
```
## Expand-Contract 三阶段示例(字段重命名 username → display_name
Phase 1 EXPAND: 添加 display_name nullable。部署 v2 双写。
Phase 2 MIGRATE: 独立迁移文件批量 UPDATE 回填。部署 v3 读新写双。验证一致性。
Phase 3 CONTRACT: 删除 username。部署 v4 仅新字段。
时间线Day 1 加字段+双写Day 2 回填Day 3 读新写双+验证Day 7 删旧字段。
## 批量数据迁移模板
```php
$batchSize = 2000;
$lastId = 0;
while (true) {
$affected = Db::update("UPDATE users SET normalized_email = LOWER(email) WHERE id > ? AND normalized_email IS NULL ORDER BY id LIMIT ?", [$lastId, $batchSize]);
if ($affected === 0) break;
$lastId = Db::selectOne("SELECT MAX(id) AS max_id FROM users WHERE normalized_email IS NOT NULL AND id > ?", [$lastId])->max_id ?? $lastId + $batchSize;
usleep(100_000);
}
```
规则10005000 行/批、批次间 sleep、游标 WHERE id > ?、低峰期执行。
## 大表变更策略(百万级+
| 操作 | 风险 | 方案 |
|------|------|------|
| ADD COLUMN | 低 | 直接执行nullable 或有默认值 |
| ADD INDEX | 中 | ALGORITHM=INPLACE, LOCK=NONE |
| DROP COLUMN | 高 | 先移除代码→部署→下版本迁移删除 |
| ALTER TYPE | 高 | Expand-Contract |
| RENAME COLUMN | 高 | Expand-Contract |
| DROP TABLE | 极高 | 先重命名→观察一周→删除 |
## 危险操作清单
| 操作 | 缓解 |
|------|------|
| DROP TABLE | 先备份 |
| DROP COLUMN | 先移除代码引用 |
| ALTER TYPE | Expand-Contract |
| TRUNCATE | 禁止生产 |
| NOT NULL 无默认值 | 先 nullable→回填→再加约束 |

View File

@@ -0,0 +1,57 @@
# 数据库回滚模式 (Hyperf Migration)
## 安全回滚策略
### 添加字段(可回滚)
```php
// Migration up()
Schema::table('users', function (Blueprint $table) {
$table->string('avatar_url')->nullable()->after('email');
});
// Migration down()
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('avatar_url');
});
```
### 删除字段(不可逆 — 需预备份)
```sql
-- 删除前先备份
CREATE TABLE _backup_users_phone AS
SELECT id, phone FROM users WHERE phone IS NOT NULL;
```
### 重命名字段(分步迁移)
```php
// Step 1: 添加新列
Schema::table('users', function (Blueprint $table) {
$table->string('display_name')->nullable()->after('name');
});
// Step 2: 迁移数据
DB::statement('UPDATE users SET display_name = name');
// Step 3: 删除旧列(下一个迁移文件)
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('name');
});
```
### 类型变更(分步迁移)
```
Step 1: 添加新列 → Step 2: 迁移数据 → Step 3: 删除旧列
```
## 回滚命令
```bash
# 回滚最近一次迁移
php bin/hyperf.php migrate:rollback
# 回滚最近 N 次迁移
php bin/hyperf.php migrate:rollback --step=3
# 查看迁移状态
php bin/hyperf.php migrate:status
```

View File

@@ -0,0 +1,184 @@
---
name: debugging
version: 2.0.0
description: "七步法系统化调试工作流。当用户报告 Bug、错误、异常、截图指出界面问题或功能与预期不符时使用。含信号解析和结构化调试报告。"
---
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-debugging.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Debugging Workflow
## 触发条件
用户报告错误、异常、意外行为,或请求排查问题。包含但不限于:
| 来源 | 典型表现 | 示例 |
|---|---|---|
| 运行时错误 | 控制台报错、崩溃、白屏 | "点击按钮报 500" |
| 视觉/UI 异常 | 组件显示与预期不符 | "暗色模式下输入框是白色" |
| 截图指出问题 | 用户提供截图标注异常区域 | 附图 + "这里不对" |
| 功能失效 | 操作无响应或结果错误 | "筛选没有效果" |
| 样式回归 | 改动后样式错乱 | "更新后布局乱了" |
> ⚠️ **截图 = Bug 报告**:用户提供截图并指出问题,本质是视觉 Bug 报告,必须激活本技能。
## 七步法
### -1. 截图前置守卫 (Screenshot Guard)
收到用户消息时,**先检查输入形式**,再分析文字意图:
| 输入形式 | 问题关键词 | 判定 |
|---------|-----------|------|
| 包含截图/图片 | 不合理 / 不对 / 有问题 / 调整 / 修复 / 偏了 / 太大 / 太小 / 空白 / 溢出 / 错位 | → 视觉 Bug激活完整调试流程 |
| 包含截图/图片 | 新增 / 添加 / 改为 / 参考这个 / 照着做 | → 需求变更,走正常开发流程 |
**硬规则**:无论判定结果是哪条路径,都**必须在回复开头输出守卫判定块**
```
## 截图守卫判定
- 输入形式:包含截图 ✓
- 关键词扫描「XXX」→ 命中 [视觉 Bug / 需求变更] 类
- 判定:[激活调试流程 / 走正常开发流程]
```
**视觉 Bug 路径规则**
- 截图 + 问题描述 = 视觉 Bug**不得降级为 L1 直接执行**,最低 L2
- 必须走完信号解析 → 修复 → 回归验证(含 chrome-devtools 截图对比)全流程
**需求变更路径规则**
- 截图用于"参考设计"或"说明改动目标",走正常开发流程
- 复杂度按 001-workflow 标准判定(可为 L1
### 0. 信号解析 (Parse Signals)
从用户描述中**主动提取**以下关键信号(缺失的主动追问):
| 信号 | 来源 | 重要性 |
|---|---|---|
| 错误消息 / 堆栈追踪 | 控制台、日志、浏览器 DevTools | 🔴 必需 |
| 复现步骤 | 用户操作序列 | 🔴 必需 |
| 截图标注的异常区域 | 用户截图 + 文字描述 | 🔴 必需(有截图时) |
| 影响区域 | 后端服务 / 前端组件 / API / DB | 🟠 重要 |
| 环境信息 | PHP 版本 / 浏览器 / OS | 🟡 有用 |
| 上次正常的时间/版本 | Git log / 部署记录 | 🟡 有用 |
| 偶发 vs 稳定 | 复现概率 | 🟡 有用 |
### 0.5 路由到调试策略 (Route to Debug Strategy)
根据影响区域选择最高效的调试工具:
| 影响区域 | 首选调试方式 | 测试工具 | 关键位置 |
|---|---|---|---|
| PHP Service 逻辑 | 断点 + 日志 | PHPUnit | `Case-Database-Backend/test/Unit/` |
| API 端点 | Postman / curl + 日志 | Hyperf HttpClient | `Case-Database-Backend/test/Feature/` |
| 数据库查询 | SQL 日志 + `EXPLAIN` | PHPUnit + SQLite | `Case-Database-Backend/test/Unit/` |
| Vue 组件渲染 | Vue DevTools | Vitest + VTU | `frontend-*/src/**/*.test.ts` |
| Vue 状态管理 | Pinia DevTools | Vitest | `frontend-*/src/**/*.test.ts` |
| API 请求/响应 | 浏览器 Network 面板 | Mock + Vitest | `frontend-*/src/**/*.test.ts` |
| 样式/布局 | Chrome DevTools Elements | 视觉回归 | — |
| Swoole 协程 | `var_dump` + Hyperf 日志 | PHPUnit | `Case-Database-Backend/test/` |
| WebSocket | 浏览器 WS 面板 + 服务端日志 | Swoole Testing | `Case-Database-Backend/test/` |
### 1. 复现 (Reproduce)
- 获取准确的错误信息、堆栈追踪
- 确认可稳定复现的步骤
- 确认环境:浏览器/Node 版本、OS
- **关键**:如果无法稳定复现,记录复现概率和条件
### 2. 收集 (Collect)
- 读取相关源码文件
- 检查最近的 git 变更:`git log --oneline -10 -- <affected-path>`
- 查看相关日志
- 检查环境变量配置
- **新增**:查看该区域的已有测试,了解预期行为
### 3. 假设 (Hypothesize)
列出 2-3 个最可能的原因,按概率排序:
```
假设 A (70%): <描述> — 因为 <证据>
假设 B (20%): <描述> — 因为 <证据>
假设 C (10%): <描述> — 因为 <证据>
```
每个假设必须指向**具体文件和代码行**。
### 4. 验证 (Verify)
从最高概率假设开始验证。每次只验证一个假设:
- 添加 `console.log` / 断点 / Hyperf 日志
- 写最小复现测试用例(参考 `bug-reproduce` 技能)
- 检查相关数据状态
### 5. 修复 (Fix)
- 修复根因,不是症状
- 修改尽可能小
- 添加防御性代码防止复发
### 6. 回归 (Regress)
- 确认原始问题已修复
- 运行相关测试套件
- 检查是否引入新问题
- **新增**:编写回归测试防止复发
## 硬停止条件
遇到以下情况时**立即停止调试**,告知用户需要额外条件:
| 条件 | 原因 | 建议 |
|---|---|---|
| 需要真实第三方 API 凭证 | 无法在本地模拟 | 提供测试凭证或 Mock 服务 |
| 竞态条件/时序依赖 | 不可靠的复现 | 添加日志收集并发数据 |
| 需要生产数据 | 本地无法重建状态 | 导出脱敏数据子集 |
| 需要特定基础设施 | 本地无法模拟 | 在测试环境调试 |
| 超过 3 次假设全部否定 | 信息不足 | 收集更多日志和上下文 |
## 调试报告模板
```markdown
## 🔧 调试报告
**问题**: [一句话描述]
**状态**: ✅ 已修复 | ⏳ 进行中 | ❌ 需要更多信息
### 信号
- 错误: [错误消息]
- 区域: [影响区域]
- 复现: [步骤]
### 根因
[1-2 句话解释 Bug 产生的机制]
### 修复
| 文件 | 修改 |
|---|---|
| `path/to/file` | [修改说明] |
### 回归测试
- `path/to/test` — [测试说明]
### 验证
- [ ] 原始问题已修复
- [ ] 回归测试通过
- [ ] 无新问题引入
```
## 常见问题速查
详见 `references/common-errors.md`
## 验证
1. [ ] 提取了所有关键信号(错误消息、复现步骤、影响区域)
2. [ ] 选择了正确的调试策略和工具
3. [ ] 原始问题已修复
4. [ ] 所有现有测试通过
5. [ ] 添加了防止复发的回归测试
6. [ ] 修复范围最小化
7. [ ] 输出了结构化调试报告

View File

@@ -0,0 +1,37 @@
# 常见错误速查表
## Vue 3
| 错误 | 原因 | 修复 |
|------|------|------|
| `[Vue warn] Missing required prop` | 必填 prop 未传入 | 检查父组件传参或设置默认值 |
| `Maximum recursive updates` | 响应式数据在 watch 中循环修改 | 添加条件判断或用 `watchEffect` 替代 |
| `inject() can only be called inside setup()` | 依赖注入在非 setup 中调用 | 移入 `<script setup>``setup()` 函数 |
| `Extraneous non-props attributes` | 组件未声明 `inheritAttrs: false` | 添加 `defineOptions({ inheritAttrs: false })` |
| `Component is missing template` | 组件缺少 template 或 render | 检查 .vue 文件是否有 `<template>` |
## TypeScript
| 错误 | 原因 | 修复 |
|------|------|------|
| `Cannot read properties of undefined` | 访问未定义对象属性 | 增加空值判断或使用可选链 `?.` |
| `Unexpected token 'export'` | 运行环境与模块格式不匹配 | 检查 ESM/CJS 配置与运行命令 |
| `x is not a function` | 导入或变量类型错误 | 检查导出方式与调用处参数 |
## PHP Hyperf
| 错误 | 原因 | 修复 |
|------|------|------|
| `Class not found` | DI 容器未注册或命名空间错误 | 检查 namespace、运行 `composer dump-autoload` |
| `Connection pool exhausted` | 连接池已满,协程等待超时 | 增大 `max_connections` 或减少连接占用时间 |
| `Coroutine context destroyed` | 在协程外访问协程上下文 | 使用 `Context::get()` 前确保在协程环境 |
| `Table not found` | 迁移未执行 | 运行 `php bin/hyperf.php migrate` |
| `Allowed memory exhausted` | 内存泄漏或大数据未分块 | 使用 `chunk()` 处理大数据集 |
## MySQL
| 错误 | 原因 | 修复 |
|------|------|------|
| Duplicate entry (1062) | 插入重复唯一键数据 | 用 `INSERT ... ON DUPLICATE KEY UPDATE` 或先查询 |
| Lock wait timeout (1205) | 事务死锁或长事务 | 缩短事务范围,添加合适索引 |
| Too many connections (1040) | 连接数超限 | 检查连接池配置,增大 `max_connections` |

View File

@@ -0,0 +1,73 @@
---
name: documentation
version: 1.0.0
description: "生成并更新项目文档。当需要撰写技术文档、README、API 文档或架构决策记录时使用。"
---
# Documentation Generator
## 触发条件
用户要求创建、更新或改进项目文档。
## 执行流程
### 1. 确定文档类型
| 类型 | 输出位置 | 模板来源 |
|------|---------|---------|
| PRD | `docs/vision/PRD.md` | 已有模板,直接填写 |
| README | `README.md` | 项目概述 + 安装 + 使用 |
| API 文档 | `docs/architecture/api-contracts.md` | 端点 + 请求/响应 |
| 架构文档 | `docs/architecture/system-design.md` | 已有模板,直接填写 |
| 数据模型 | `docs/architecture/data-model.md` | 已有模板,直接填写 |
| ADR | `docs/architecture/decisions/NNN-*.md` | 复制 `_template.md` 后填写 |
| 组件文档 | JSDoc / Storybook | Props + 示例 |
| 变更日志 | `CHANGELOG.md` | Conventional Changelog |
> **模板路径**: 创建 ADR 时,先读取 `docs/architecture/decisions/_template.md` 作为基础。
> PRD、架构文档、数据模型已在 `docs/` 下有完整模板,优先填充而非重新创建。
### 2. 收集上下文
1. 读取相关源码理解功能
2. 读取已有文档确认风格
3. 读取 `package.json` 获取项目信息
4. 读取 `docs/guides/style-guide.md` 确认文档规范
### 3. 生成文档
遵循以下写作原则:
- **简明**:一句话说清楚,不要冗余描述
- **可执行**:代码示例必须可运行
- **可维护**:避免写死版本号和路径
- **受众明确**:新人 onboarding vs 日常参考 vs 架构决策
### 4. ADR 创建流程
1. 复制模板:`docs/architecture/decisions/_template.md``docs/architecture/decisions/NNN-title.md`
2. 替换 `XXX` 为递增编号(检查已有 ADR 文件确定下一个编号)
3. 填写所有章节:状态、上下文(含问题陈述和约束)、决策、备选方案、理由、影响
4. 标注记录人和审核人
### 5. JSDoc 规范
```typescript
/**
* 一句话描述。
*
* @param paramName - 参数说明
* @returns 返回值说明
* @throws {ErrorType} 何时抛出
*
* @example
* const result = functionName('input');
*/
```
## 验证
1. [ ] 文档无拼写错误
2. [ ] 代码示例可运行
3. [ ] 链接有效
4. [ ] 遵循项目文档风格

View File

@@ -0,0 +1,31 @@
# 文档写作指南
## 写作原则
1. **一句话规则**:每个概念用一句话解释清楚
2. **Show, Don't Tell**:用代码示例代替文字描述
3. **链接优于重复**:引用已有文档而非复制粘贴
4. **面向受众**:明确文档读者是谁
## Markdown 规范
- 标题层级不超过 4 级(`#``####`
- 代码块指定语言(```typescript / ```php / ```bash 等,而非 ```
- 表格对齐(使用 Prettier
- 链接使用相对路径
## README 结构
```markdown
# 项目名
一句话描述。
## 快速开始
## 功能特性
## 安装
## 使用
## 配置
## 贡献指南
## 许可证
```

View File

@@ -0,0 +1,250 @@
---
name: env-setup
version: 2.0.0
description: "初始化 PHP Hyperf + Vue 3 双栈开发环境。当需要项目初始化、环境配置或安装依赖时使用。含 Docker Compose 和数据库初始化。"
---
# 🔧 Environment Setup (PHP Hyperf + Vue 3 Dual Stack)
## 触发条件
用户要求初始化项目环境、配置开发工具链、设置环境变量。
## 执行流程
### 1. 检测系统依赖
```bash
echo "=== System Dependencies ==="
# PHP
php -v 2>/dev/null && echo "✅ PHP installed" || echo "❌ PHP not found (need >= 8.1)"
# Swoole
php -m 2>/dev/null | grep -i swoole && echo "✅ Swoole installed" || echo "❌ Swoole not found (need >= 5.0)"
# Composer
composer --version 2>/dev/null && echo "✅ Composer installed" || echo "❌ Composer not found"
# Node.js
node -v 2>/dev/null && echo "✅ Node.js installed" || echo "❌ Node.js not found (need >= 20)"
# npm
npm -v 2>/dev/null && echo "✅ npm installed" || echo "❌ npm not found"
# Docker
docker --version 2>/dev/null && echo "✅ Docker installed" || echo "⚠️ Docker not found (optional for local dev)"
# Docker Compose
docker compose version 2>/dev/null && echo "✅ Docker Compose installed" || echo "⚠️ Docker Compose not found"
```
### 2. 后端初始化 (PHP Hyperf)
```bash
cd Case-Database-Backend
# 安装 PHP 依赖
composer install
# 复制环境变量
if [ ! -f ".env" ] && [ -f ".env.example" ]; then
cp .env.example .env
echo "✅ .env created from .env.example"
echo "⚠️ Please fill in required values: DB_HOST, DB_DATABASE, REDIS_HOST, JWT_SECRET"
fi
# 验证 Hyperf 启动
php bin/hyperf.php start --dry-run 2>/dev/null || php bin/hyperf.php di:init-proxy
```
### 3. 前端初始化 (Vue 3 + Vite)
```bash
for dir in Case-Database-Frontend-user Case-Database-Frontend-admin; do
echo "=== $dir ==="
cd $dir
# 安装 Node 依赖
npm install
# 复制环境变量
if [ ! -f ".env.local" ] && [ -f ".env.example" ]; then
cp .env.example .env.local
echo "✅ .env.local created from .env.example"
fi
cd ..
done
```
### 4. 数据库初始化
```bash
cd Case-Database-Backend
# 运行迁移
php bin/hyperf.php migrate
# 运行种子(如有)
php bin/hyperf.php db:seed
```
### 5. Docker Compose 启动(可选)
```bash
# 一键启动全部服务
docker compose up -d
# 验证服务状态
docker compose ps
```
### 6. 初始化工具链
| 工具 | 配置文件 | 前端/后端 |
|------|---------|---------|
| jsconfig | `jsconfig.json` | 前端 |
| ESLint | `.eslintrc.*` / `eslint.config.*` | 前端 |
| Prettier | `.prettierrc` | 前端 |
| Husky | `.husky/` | 全栈 |
| PHPStan | `phpstan.neon` | 后端 |
| PHP CS Fixer | `.php-cs-fixer.php` | 后端 |
### 7. 验证环境
```bash
echo "=== Backend Verification ==="
cd Case-Database-Backend
php -v
composer --version
php bin/hyperf.php --version 2>/dev/null || echo "Run: php bin/hyperf.php start"
echo ""
echo "=== Frontend Verification ==="
for dir in Case-Database-Frontend-user Case-Database-Frontend-admin; do
echo "=== $dir Verification ==="
cd $dir
node -v
npm -v
npm run build --dry-run 2>/dev/null || echo "Run: npm run dev"
cd ..
done
echo ""
echo "=== Services ==="
docker compose ps 2>/dev/null || echo "Docker Compose not running"
```
## 关键环境变量
### 后端 (.env)
| 变量 | 说明 | 示例 |
|------|------|------|
| `APP_NAME` | 应用名称 | `MyApp` |
| `APP_ENV` | 环境 | `dev` / `production` |
| `DB_HOST` | MySQL 主机 | `127.0.0.1` |
| `DB_PORT` | MySQL 端口 | `3306` |
| `DB_DATABASE` | 数据库名 | `myapp` |
| `DB_USERNAME` | 数据库用户 | `root` |
| `DB_PASSWORD` | 数据库密码 | *(Secret)* |
| `REDIS_HOST` | Redis 主机 | `127.0.0.1` |
| `REDIS_PORT` | Redis 端口 | `6379` |
| `JWT_SECRET` | JWT 密钥 | *(auto-generate)* |
### 前端 (.env.local)
| 变量 | 说明 | 示例 |
|------|------|------|
| `VITE_API_BASE_URL` | API 地址 | `http://localhost:9501` |
| `VITE_WS_URL` | WebSocket 地址 | `ws://localhost:9502` |
| `VITE_APP_TITLE` | 应用标题 | `MyApp` |
## 密钥管理最佳实践
### 原则
- **永不硬编码** — 密钥不进入代码仓库
- **运行时注入** — 密钥在部署/启动时注入,不写入磁盘文件
- **最小权限** — 每个服务只拥有必需的密钥访问权限
- **定期轮换** — 密钥和 Token 设置过期时间并定期轮换
### 推荐方案(按场景选择)
| 场景 | 方案 | 复杂度 |
|---|---|---|
| 本地开发 | `.env` 文件 (已在 .gitignore) | 🟢 低 |
| CI/CD | GitHub Secrets / GitLab Variables | 🟡 中 |
| 生产环境 | HashiCorp Vault / 云 KMS | 🔴 高 |
| Docker 部署 | Docker Secrets / Compose env_file | 🟡 中 |
### 本地开发密钥管理
```bash
# ✅ .env 文件必须在 .gitignore 中
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
# ✅ 提供 .env.example 作为模板(无真实密钥)
cp .env .env.example
# 手动清空 .env.example 中的敏感值
```
### Docker 部署密钥注入
```yaml
# ❌ BAD: docker-compose.yml 中硬编码密钥
services:
app:
environment:
DB_PASSWORD: "my_secret_password"
# ✅ GOOD: 通过 .env 文件注入
services:
app:
env_file:
- .env
environment:
DB_PASSWORD: ${DB_PASSWORD}
```
### JWT 密钥生成
```bash
# 生成强随机 JWT 密钥
openssl rand -base64 64
# 或使用 PHP
php -r "echo bin2hex(random_bytes(32));"
```
### 检查密钥安全
```bash
# 检查是否有密钥意外提交到仓库
rg -rn "(?i)(api.?key|secret|password|token)\s*[=:]\s*['\"][a-zA-Z0-9]{8,}" \
--glob '!vendor/**' --glob '!node_modules/**' --glob '!*.lock' --glob '!.env*'
```
## 常见问题排查
| 问题 | 解决方案 |
|------|---------|
| Swoole 未安装 | `pecl install swoole` 或使用 Docker |
| Composer 依赖失败 | `composer clear-cache && composer install` |
| Node 版本不匹配 | 使用 `nvm use 20` 切换版本 |
| MySQL 连接失败 | 检查 `.env` 中 DB_HOST 和端口,确认 MySQL 正在运行 |
| Redis 连接失败 | 检查 `.env` 中 REDIS_HOST确认 Redis 正在运行 |
| Hyperf 启动失败 | 检查 `runtime/` 目录权限,运行 `php bin/hyperf.php di:init-proxy` |
| 端口冲突 9501 | `lsof -i :9501` 查看占用进程 |
## 验证清单
- [ ] PHP >= 8.1 且 Swoole >= 5.0 扩展已安装
- [ ] Composer 依赖安装成功
- [ ] Node.js >= 20 且 npm 安装成功
- [ ] `php bin/hyperf.php start` 可正常启动 (HTTP 9501)
- [ ] `npm run dev` 可正常启动 (Vite dev server)
- [ ] MySQL 和 Redis 连接正常
- [ ] 数据库迁移成功
- [ ] 所有环境变量已配置

View File

@@ -0,0 +1,128 @@
---
name: full-feature
version: 2.1.0
description: "Vue 3 + Hyperf 端到端功能开发工作流。当需要从数据库到 API 到 UI 全链路开发完整功能时使用。编排多个技能协同工作。"
requires: [component-scaffold, vue-testing]
---
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-full-feature.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Full Feature Workflow (Vue 3 + Hyperf)
## 触发条件
用户要求开发一个完整功能(前端 + 后端 + 数据库 + 测试)。
## 执行流程
### Phase 1: 规划
1. **拆分功能为子任务**
- 数据层:需要什么数据模型和数据库变更?
- 后端 API需要哪些 Controller/Service/Repository
- 前端 UI需要哪些组件和页面
- 测试:每层需要什么测试?
2. **确认技术决策**(向用户确认有争议的选择)
3. **输出执行计划**
```
## 功能: <功能名称>
### 数据层
- [ ] Hyperf Migration — 创建/修改表
- [ ] Model 定义 — 关联、Casts、Fillable
### 后端 API 层
- [ ] POST /admin/<resources> — 创建
- [ ] GET /admin/<resources> — 列表
- [ ] GET /admin/<resources>/:id — 详情
- [ ] PUT /admin/<resources>/:id — 更新
- [ ] DELETE /admin/<resources>/:id — 删除
- [ ] Service 业务逻辑
- [ ] FormRequest 参数验证
- [ ] 路由注册 + 中间件
### 前端 UI 层
- [ ] API 接口封装 (src/api/<module>)
- [ ] 列表页 (src/views/<module>/<resource>/index.vue)
- [ ] 表单组件 (src/components/custom/<Resource>Form.vue)
- [ ] 详情页 (可选)
- [ ] 路由配置 (src/router/routes/<module>.ts)
- [ ] Store 模块 (如需跨页面状态)
### 测试
- [ ] PHPUnit — Service 层测试
- [ ] Vitest — 组件渲染测试
```
### Phase 2: 数据层(使用 database-migration 技能)
1. 生成迁移: `php bin/hyperf.php gen:migration create_<table>_table`
2. 编写迁移(遵循高并发表设计规范)
3. 执行迁移: `php bin/hyperf.php migrate`
4. 生成 Model: `php bin/hyperf.php gen:model <table>`
5. 补充 Model 关联和类型转换
### Phase 3: 后端 API 层(使用 api-scaffold + hyperf-service 技能)
1. 创建 Controller接收请求、调用 Service
2. 创建 Service核心业务逻辑、事务管理
3. 创建 Repository可选复杂查询时使用
4. 创建 FormRequest输入验证
5. 注册路由 + 挂载中间件
6. 编写 PHPUnit 测试
### Phase 4: 前端 UI 层(使用 component-scaffold + vue-page 技能)
1. 封装 API 接口
```typescript
// src/api/<module>/<resource>.ts
import request from '@/utils/request'
export const {{resource}}Api = {
list: (params) => request.get('/admin/{{resources}}', { params }),
getById: (id) => request.get(`/admin/{{resources}}/${id}`),
create: (data) => request.post('/admin/{{resources}}', data),
update: (id, data) => request.put(`/admin/{{resources}}/${id}`, data),
delete: (id) => request.delete(`/admin/{{resources}}/${id}`),
}
```
2. 创建列表页管理端Element Plus 表格 + 分页用户端Tailwind 卡片/表格 + 自定义分页,禁止 Element Plus
3. 创建表单组件管理端Element Plus Form + 验证用户端Headless UI + Tailwind 表单,禁止 Element Plus
4. 创建详情页(如需要)
5. 配置 Vue Router 路由
6. 连接 API 到 Pinia Store如需跨页面状态
### Phase 5: 集成验证
1. 后端测试: `composer test`
2. 后端静态分析: `composer analyse`
3. 前端 Lint 检查: `npm run lint`
5. 手动测试功能流程CRUD 全路径)
### Phase 6: 收尾
1. 更新相关文档data-model.md、api-contracts.md
2. Git commit遵循 Conventional Commits
3. 汇报完成状态
## 执行原则
- **自底向上**:先数据层,再后端 API再前端 UI
- **每步验证**:每个 Phase 完成后运行测试
- **可中断**:每个 Phase 独立可提交
- **用已有技能**:尽量调用其他 Skill 而非从零写
- **前后端分离**API 契约先行,前后端可并行开发
## 验证
1. [ ] 后端测试全部通过
2. [ ] 前端 ESLint 无报错
3. [ ] 功能端到端可用(创建、列表、详情、编辑、删除)
4. [ ] 代码遵循项目现有模式
5. [ ] 文档已更新

View File

@@ -0,0 +1,69 @@
---
name: hyperf-service
version: 2.0.0
description: "生成 Hyperf 后端服务模块脚手架Controller/Service/Repository/Model。当需要新建后端业务模块时使用。支持 DI 注入和事务管理。"
---
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-backend-scaffold.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Hyperf Service Module Scaffold
## 触发条件
用户要求创建后端服务模块、业务 Service 类、或完整的后端业务层。
## 执行流程
### 0. 加载规范(⚠️ 必须最先执行)
依次读取 `.cursor/rules/013-backend.mdc``016-swoole.mdc``019-modular.mdc`,提取 DI、事务、协程安全、依赖方向、事件解耦。
### 1. 确认模块规格
| 字段 | 必填 | 默认值 |
|------|------|--------|
| 模块名称 | ✅ | — |
| 所属业务域 | ❌ | 推断 |
| 需要 Repository | ❌ | 复杂查询时 true |
| 需要事件? | ❌ | false |
| 需要数据权限? | ❌ | true |
| 需要缓存? | ❌ | false |
### 2. 生成文件结构
Controller/Admin、Service、Repository、Model、Request、Event、Listener可选
### 3. Service / Repository
Service 使用 `Db::transaction` 包裹写操作Repository 实现 `applyFilters``applyDataScope`、分页上限。完整模板见 **Tier 3**
### 4. 结构化日志
Service 集成 HasLogger Traitcreate/update/delete 至少 info 级别。RequestId 中间件全局注入 request_id。
### 5. 重试机制
外部 API 调用使用 RetryHelper 指数退避重试retryOn 指定可重试异常类型。
### 6. 遵循项目约定
扫描 `app/Service/`、AbstractService、BusinessException、数据权限、事件注册、HasLogger、RetryHelper。
## 验证
1. [ ] `php -l` 无错误
2. [ ] 依赖注入正确
3. [ ] 写操作使用 `Db::transaction()`
4. [ ] 分页有 page_size 上限
5. [ ] 异常使用 BusinessException
6. [ ] applyDataScope 已实现(如需)
7. [ ] 关键操作有结构化日志
8. [ ] 外部 API 用 RetryHelper
9. [ ] Logger channel 使用类名
## Tier 3 深度参考
| 文件 | 内容 |
|------|------|
| `references/service-templates.md` | Service/Repository/Event/Logger/Retry 完整代码 |

View File

@@ -0,0 +1,120 @@
# Hyperf Service — 代码模板
> 主流程见 SKILL.md本文档为 Service/Repository/Event/Logger/Retry 完整实现。
## Service 模板
```php
<?php
namespace App\Service\{{Domain}};
use App\Model\{{Domain}}\{{Module}};
use App\Repository\{{Domain}}\{{Module}}Repository;
use App\Exception\BusinessException;
use Hyperf\DbConnection\Db;
use Hyperf\Di\Annotation\Inject;
class {{Module}}Service
{
#[Inject]
protected {{Module}}Repository $repository;
public function getPageList(array $params): array { return $this->repository->getPageList($params); }
public function getById(int $id): {{Module}} {
$record = {{Module}}::find($id);
if (!$record) throw new BusinessException(404, '{{Module}} not found');
return $record;
}
public function create(array $data): {{Module}} {
return Db::transaction(function () use ($data) {
$record = {{Module}}::create($data);
return $record;
});
}
public function update(int $id, array $data): {{Module}} {
$record = $this->getById($id);
return Db::transaction(function () use ($record, $data) {
$record->update($data);
return $record->refresh();
});
}
public function delete(int $id): void {
$record = $this->getById($id);
Db::transaction(fn () => $record->delete());
}
}
```
## Repository 模板(含 applyFilters / applyDataScope
```php
class {{Module}}Repository
{
public function getPageList(array $params): array {
$query = {{Module}}::query();
$this->applyFilters($query, $params);
$this->applyDataScope($query);
$page = (int) ($params['page'] ?? 1);
$pageSize = min((int) ($params['page_size'] ?? 10), 100);
$total = $query->count();
$items = $query->with($this->getDefaultRelations())->orderByDesc('id')
->offset(($page - 1) * $pageSize)->limit($pageSize)->get();
return ['items' => $items, 'total' => $total];
}
protected function applyFilters(Builder $query, array $params): void {
if (!empty($params['keyword'])) $query->where('name', 'like', "%{$params['keyword']}%");
if (!empty($params['status'])) $query->where('status', $params['status']);
}
protected function applyDataScope(Builder $query): void { /* 数据权限 */ }
protected function getDefaultRelations(): array { return []; }
}
```
## Event + Listener 模板
```php
// Event
class {{Module}}Created { public function __construct(public readonly {{Module}} $record) {} }
// Listener
#[Listener]
class {{Module}}CreatedListener implements ListenerInterface
{
public function listen(): array { return [{{Module}}Created::class]; }
public function process(object $event): void { /* 发送通知、更新缓存等 */ }
}
```
## HasLogger Trait
```php
trait HasLogger {
protected ?LoggerInterface $logger = null;
protected function logger(): LoggerInterface { /* 按类名获取 channel */ }
protected function logContext(array $extra = []): array { return array_merge(['request_id' => Context::get('request_id'), 'user_id' => Context::get('current_user_id')], $extra); }
}
```
## RetryHelper
```php
public static function withRetry(callable $fn, int $maxRetries = 3, int $baseDelayMs = 1000, array $retryOn = []): mixed {
for ($attempt = 0; $attempt <= $maxRetries; $attempt++) {
try { return $fn(); }
catch (\Throwable $e) {
if ($attempt < $maxRetries) Coroutine::sleep(($baseDelayMs * (2 ** $attempt) + jitter) / 1000);
else throw $e;
}
}
}
```
## RequestIdMiddleware
在 process 中:`Context::set('request_id', $requestId)`,响应头加 `X-Request-ID`

View File

@@ -0,0 +1,253 @@
---
name: i18n
version: 3.0.0
description: "使用 vue-i18n 管理 Vue 3 应用的国际化。当需要添加多语言支持或管理翻译时使用。含语言包管理和路由国际化。"
---
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-i18n.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Internationalization (i18n) — Vue 3 + vue-i18n
## 触发条件
用户要求添加多语言支持、翻译内容、配置国际化路由。
## 执行流程
### 1. 检测现有 i18n 方案
```bash
# 检查是否已有 i18n 库
grep -E "vue-i18n|@intlify" package.json 2>/dev/null
ls src/i18n* src/locales* 2>/dev/null
```
### 2. 初始化 i18n如未配置
推荐方案:`vue-i18n`Vue 3 官方国际化方案)
```bash
npm install vue-i18n@9
```
创建目录结构:
```
src/locales/
├── index.ts # i18n 实例配置
├── zh-CN.ts # 中文(简体)— 使用 JS 扁平对象格式
├── en.ts # 英文 — 使用 JS 扁平对象格式
└── modules/ # 按模块拆分(大型项目,同样使用 JS 格式)
├── common.zh-CN.ts
└── common.en.ts
```
> **格式选择**:统一使用 `.ts` 文件(`export default { 'key': 'value' }`
> 而非 `.json`。原因JS 格式支持注释、支持函数(复数规则)、支持扁平键名中的特殊字符,
> 且与 vue-i18n v9 的 Composition API 配合更灵活。
> 若项目已使用 `.json`,保持现有格式,不强制迁移。
### 3. i18n 配置
```typescript
// src/locales/index.ts
import { createI18n } from 'vue-i18n'
import zhCN from './zh-CN.ts'
import en from './en.ts'
const i18n = createI18n({
legacy: false,
locale: localStorage.getItem('locale') || 'zh-CN',
fallbackLocale: 'en',
messages: {
'zh-CN': zhCN,
en,
},
})
export default i18n
```
```typescript
// src/main.ts
import i18n from './locales'
app.use(i18n)
```
### 4. 翻译键命名规范
**使用扁平 dot notation**(不使用嵌套对象),避免深层路径冲突:
```typescript
// ✅ 扁平 dot notation — 清晰、无歧义、工具友好
export default {
'common.action.save': '保存',
'common.action.cancel': '取消',
'common.action.delete': '删除',
'common.status.loading': '加载中...',
'auth.action.login': '登录',
'auth.action.logout': '退出登录',
'auth.action.signUp': '注册',
'home.hero.title': '欢迎',
'home.hero.description': '...',
}
// ❌ 嵌套对象 — 容易产生路径冲突
export default {
common: { save: '保存' },
pages: { home: { title: '...' } },
}
```
**命名公式**`{feature}.{context}.{action|status|label}`
| 段 | 含义 | 示例 |
|---|---|---|
| `feature` | 业务功能/模块 | `order``user``common` |
| `context` | 所在 UI 区域 | `list``form``dialog``action` |
| `action\|status\|label` | 具体语义 | `submit``loading``title` |
```typescript
// 示例
'order.list.title': '订单列表',
'order.form.submit': '提交订单',
'order.status.pending': '待处理',
'order.dialog.deleteConfirm': '确认删除该订单?',
'user.profile.edit': '编辑资料',
```
**键冲突预防**
```typescript
// ❌ 冲突:'order.edit' 同时是叶子节点和父节点前缀
'order.edit': '编辑',
'order.edit.title': '编辑订单',
// ✅ 修正:给叶子节点加上语义后缀
'order.edit.action': '编辑',
'order.edit.title': '编辑订单',
```
**参数化翻译**
```typescript
// 语言包
'order.total': '共 {count} 件商品,合计 {amount}',
'user.greeting': '你好,{name}',
// 组件中使用
t('order.total', { count: 5, amount: '¥100.00' })
t('user.greeting', { name: user.name })
```
**标准命名空间**(根据项目模块预定义,禁止随意创建):
| 命名空间 | 用途 | 文件 |
|----------|------|------|
| `common` | 通用 UI按钮、状态、提示 | `common.ts` |
| `auth` | 登录/注册/权限 | `auth.ts` |
| `menu` | 侧边栏/导航菜单 | `menu.ts` |
| `validation` | 表单校验提示 | `validation.ts` |
| `error` | 错误码/错误提示 | `error.ts` |
| `{module}` | 各业务模块 (order/user/...) | `{module}.ts` |
新增命名空间时必须在此表中登记。
### 5. 组件中使用
```vue
<script setup>
import { useI18n } from 'vue-i18n'
const { t, locale } = useI18n()
function switchLanguage(lang) {
locale.value = lang
localStorage.setItem('locale', lang)
}
</script>
<template>
<!-- 管理端 -->
<el-button @click="switchLanguage('en')">English</el-button>
<el-button @click="switchLanguage('zh-CN')">中文</el-button>
<!-- 用户端禁止 Element Plus -->
<button class="px-3 py-1 rounded border" @click="switchLanguage('en')">English</button>
<button class="px-3 py-1 rounded border" @click="switchLanguage('zh-CN')">中文</button>
<!-- 基础用法 -->
<p>{{ t('common.action.save') }}</p>
<!-- 参数化翻译 -->
<p>{{ t('order.total', { count: orderCount, amount: totalAmount }) }}</p>
<!-- 管理端 Element Plus 组件中使用 -->
<el-button type="primary">{{ t('common.action.save') }}</el-button>
<el-input :placeholder="t('order.form.searchPlaceholder')" />
<!-- 用户端 Tailwind 组件中使用 -->
<button class="bg-blue-600 text-white px-4 py-2 rounded-lg">{{ t('common.action.save') }}</button>
<input class="border rounded-lg px-3 py-2" :placeholder="t('order.form.searchPlaceholder')" />
</template>
```
### 6. Element Plus 国际化(仅管理端)
> 以下内容仅适用于 `Case-Database-Frontend-admin/`,用户端无需配置 Element Plus 国际化。
`main.ts` 不在 setup 上下文中,无法使用 `computed` 做响应式切换。正确方案:`main.ts` 静态初始化,`App.vue` 中通过 `computed` + `ElConfigProvider` 动态响应:
```typescript
// src/main.ts
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'
// 初始化时根据 locale 选择语言包
const elLocaleMap = { 'zh-CN': zhCn, en }
const initLocale = localStorage.getItem('locale') || 'zh-CN'
app.use(ElementPlus, { locale: elLocaleMap[initLocale] ?? zhCn })
```
```vue
<!-- 在根组件 App.vue 中动态响应语言切换 -->
<script setup>
import { useI18n } from 'vue-i18n'
import { computed } from 'vue'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'
const { locale } = useI18n()
const elLocaleMap = { 'zh-CN': zhCn, en }
// 通过 ElConfigProvider 的 locale prop 动态切换
const elLocale = computed(() => elLocaleMap[locale.value] ?? zhCn)
</script>
<template>
<el-config-provider :locale="elLocale">
<router-view />
</el-config-provider>
</template>
```
### 7. 添加新语言
1.`src/locales/` 下创建新的 locale JS 文件(如 `fr.ts`
2.`src/locales/index.ts` 中注册新语言
3. 翻译所有已有 key
4. 管理端:同步 Element Plus 对应的 locale用户端跳过此步
## 验证
1. [ ] 所有 locale 文件的 key 结构一致(无遗漏)
2. [ ] 切换语言后页面正确翻译
3. [ ] 管理端Element Plus 组件语言同步切换(用户端不适用)
4. [ ] 日期/数字格式随 locale 变化
5. [ ] 键命名遵循 `{feature}.{context}.{action|status|label}` 公式
6. [ ] 使用扁平 dot notation不使用嵌套对象
7. [ ] 无键冲突(同一前缀不同时作为叶子节点和父节点前缀)
8. [ ] 参数化翻译使用 `{variableName}` 语法
9. [ ] 新命名空间已在标准命名空间表中登记

View File

@@ -0,0 +1,144 @@
---
name: mcp-builder
version: 1.1.0
description: "构建 MCP Server让 LLM 通过 Tool 与外部服务交互。当需要创建 MCP 工具或封装 API 为 MCP 时使用。推荐 TypeScript + MCP SDK。"
---
# MCP Server 开发指南
## 触发条件
用户需要构建自定义 MCP Server 以封装 API 或服务为 LLM 可调用的 Tool。
## 执行流程
### Phase 1研究与规划
#### 1.1 理解目标 API
- 梳理目标服务的 API 端点、认证方式、数据模型
- 使用 WebFetch 或 WebSearch 获取 API 文档
- 列出需要封装的端点,按优先级排序(最常用操作优先)
#### 1.2 学习 MCP 协议
**加载 MCP 规范**
- 起始页:`https://modelcontextprotocol.io/sitemap.xml`
- 获取特定页面时添加 `.md` 后缀获取 Markdown 格式
**加载 MCP SDK 文档**
- WebFetch: `https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/main/README.md`
- SDK 名称虽含 "typescript",但完全支持纯 TypeScript 项目
#### 1.3 设计 Tool
| 设计原则 | 说明 |
|---|---|
| 命名清晰 | 使用 `prefix_action_target` 格式,如 `github_create_issue` |
| 描述精准 | 简洁说明功能 + 参数含义 + 返回值结构 |
| 错误可操作 | 错误信息应引导 LLM 找到解决方案 |
| 结果聚焦 | 返回精准、相关的数据,支持分页/过滤 |
| API 覆盖优先 | 不确定时优先提供全面的 API 覆盖,而非少量高层 Workflow Tool |
---
### Phase 2实现
**推荐技术栈**
- **语言**TypeScriptMCP SDK 原生支持,无需编译,直接运行)
- **传输**:远程服务用 Streamable HTTP无状态 JSON本地用 stdio
#### 2.1 项目结构
```
mcp-server-{{name}}/
├── src/
│ ├── index.ts # Server entry + tool registration
│ ├── client.ts # API client with auth
│ └── tools/ # One file per tool group
│ ├── resources.ts
│ └── actions.ts
├── package.json
└── README.md
```
#### 2.2 基础设施
实现共享工具:
- API Client认证、请求头、Base URL
- 错误处理 Helper统一格式包含 actionable 信息)
- 响应格式化JSON 结构化 + 文本摘要)
- 分页支持
#### 2.3 Tool 实现
每个 Tool 需要:
**输入 Schema**Zod
- Zod 在纯 TypeScript 中同样可用,提供运行时参数校验
- 包含约束和清晰的字段描述
- 在 description 中添加示例值
**输出 Schema**(推荐定义):
- 使用 `outputSchema` + `structuredContent` 返回结构化数据
**Tool 注解**
- `readOnlyHint`: 是否只读
- `destructiveHint`: 是否有破坏性
- `idempotentHint`: 是否幂等
---
### Phase 3审查与测试
#### 3.1 代码质量检查
- 无重复代码DRY
- 一致的错误处理
- 关键函数和参数有 JSDoc 注释
- 清晰的 Tool 描述
#### 3.2 测试
```bash
# 直接运行验证语法
node src/index.ts
# 使用 MCP Inspector 交互式测试
npx @modelcontextprotocol/inspector
```
---
### Phase 4集成到项目
将构建好的 MCP Server 注册到 `.cursor/mcp.json`
```json
{
"mcpServers": {
"{{name}}": {
"command": "node",
"args": ["path/to/mcp-server-{{name}}/src/index.ts"],
"env": {
"API_KEY": "{{env_var}}"
}
}
}
}
```
## 验证
1. [ ] `node src/index.ts` 启动无错误
2. [ ] MCP Inspector 可以列出所有 Tool
3. [ ] 每个 Tool 的输入 Schema 有 Zod 校验
4. [ ] 每个 Tool 的 description 清晰、包含参数说明
5. [ ] 错误信息包含具体的修复建议actionable
6. [ ] 需要分页的 Tool 支持 `cursor`/`limit` 参数
7. [ ] 环境变量用于凭证/密钥,不硬编码
8. [ ] `.cursor/mcp.json` 注册正确
## Tier 3 参考资料(按需读取)
- `references/mcp_best_practices.md` — MCP 最佳实践指南
- `references/node_mcp_server.md` — TypeScript/Node.js 实现详细指南

View File

@@ -0,0 +1,249 @@
# MCP Server Best Practices
## Quick Reference
### Server Naming
- **Python**: `{service}_mcp` (e.g., `slack_mcp`)
- **Node/TypeScript**: `{service}-mcp-server` (e.g., `slack-mcp-server`)
### Tool Naming
- Use snake_case with service prefix
- Format: `{service}_{action}_{resource}`
- Example: `slack_send_message`, `github_create_issue`
### Response Formats
- Support both JSON and Markdown formats
- JSON for programmatic processing
- Markdown for human readability
### Pagination
- Always respect `limit` parameter
- Return `has_more`, `next_offset`, `total_count`
- Default to 20-50 items
### Transport
- **Streamable HTTP**: For remote servers, multi-client scenarios
- **stdio**: For local integrations, command-line tools
- Avoid SSE (deprecated in favor of streamable HTTP)
---
## Server Naming Conventions
Follow these standardized naming patterns:
**Python**: Use format `{service}_mcp` (lowercase with underscores)
- Examples: `slack_mcp`, `github_mcp`, `jira_mcp`
**Node/TypeScript**: Use format `{service}-mcp-server` (lowercase with hyphens)
- Examples: `slack-mcp-server`, `github-mcp-server`, `jira-mcp-server`
The name should be general, descriptive of the service being integrated, easy to infer from the task description, and without version numbers.
---
## Tool Naming and Design
### Tool Naming
1. **Use snake_case**: `search_users`, `create_project`, `get_channel_info`
2. **Include service prefix**: Anticipate that your MCP server may be used alongside other MCP servers
- Use `slack_send_message` instead of just `send_message`
- Use `github_create_issue` instead of just `create_issue`
3. **Be action-oriented**: Start with verbs (get, list, search, create, etc.)
4. **Be specific**: Avoid generic names that could conflict with other servers
### Tool Design
- Tool descriptions must narrowly and unambiguously describe functionality
- Descriptions must precisely match actual functionality
- Provide tool annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
- Keep tool operations focused and atomic
---
## Response Formats
All tools that return data should support multiple formats:
### JSON Format (`response_format="json"`)
- Machine-readable structured data
- Include all available fields and metadata
- Consistent field names and types
- Use for programmatic processing
### Markdown Format (`response_format="markdown"`, typically default)
- Human-readable formatted text
- Use headers, lists, and formatting for clarity
- Convert timestamps to human-readable format
- Show display names with IDs in parentheses
- Omit verbose metadata
---
## Pagination
For tools that list resources:
- **Always respect the `limit` parameter**
- **Implement pagination**: Use `offset` or cursor-based pagination
- **Return pagination metadata**: Include `has_more`, `next_offset`/`next_cursor`, `total_count`
- **Never load all results into memory**: Especially important for large datasets
- **Default to reasonable limits**: 20-50 items is typical
Example pagination response:
```json
{
"total": 150,
"count": 20,
"offset": 0,
"items": [...],
"has_more": true,
"next_offset": 20
}
```
---
## Transport Options
### Streamable HTTP
**Best for**: Remote servers, web services, multi-client scenarios
**Characteristics**:
- Bidirectional communication over HTTP
- Supports multiple simultaneous clients
- Can be deployed as a web service
- Enables server-to-client notifications
**Use when**:
- Serving multiple clients simultaneously
- Deploying as a cloud service
- Integration with web applications
### stdio
**Best for**: Local integrations, command-line tools
**Characteristics**:
- Standard input/output stream communication
- Simple setup, no network configuration needed
- Runs as a subprocess of the client
**Use when**:
- Building tools for local development environments
- Integrating with desktop applications
- Single-user, single-session scenarios
**Note**: stdio servers should NOT log to stdout (use stderr for logging)
### Transport Selection
| Criterion | stdio | Streamable HTTP |
|-----------|-------|-----------------|
| **Deployment** | Local | Remote |
| **Clients** | Single | Multiple |
| **Complexity** | Low | Medium |
| **Real-time** | No | Yes |
---
## Security Best Practices
### Authentication and Authorization
**OAuth 2.1**:
- Use secure OAuth 2.1 with certificates from recognized authorities
- Validate access tokens before processing requests
- Only accept tokens specifically intended for your server
**API Keys**:
- Store API keys in environment variables, never in code
- Validate keys on server startup
- Provide clear error messages when authentication fails
### Input Validation
- Sanitize file paths to prevent directory traversal
- Validate URLs and external identifiers
- Check parameter sizes and ranges
- Prevent command injection in system calls
- Use schema validation (Pydantic/Zod) for all inputs
### Error Handling
- Don't expose internal errors to clients
- Log security-relevant errors server-side
- Provide helpful but not revealing error messages
- Clean up resources after errors
### DNS Rebinding Protection
For streamable HTTP servers running locally:
- Enable DNS rebinding protection
- Validate the `Origin` header on all incoming connections
- Bind to `127.0.0.1` rather than `0.0.0.0`
---
## Tool Annotations
Provide annotations to help clients understand tool behavior:
| Annotation | Type | Default | Description |
|-----------|------|---------|-------------|
| `readOnlyHint` | boolean | false | Tool does not modify its environment |
| `destructiveHint` | boolean | true | Tool may perform destructive updates |
| `idempotentHint` | boolean | false | Repeated calls with same args have no additional effect |
| `openWorldHint` | boolean | true | Tool interacts with external entities |
**Important**: Annotations are hints, not security guarantees. Clients should not make security-critical decisions based solely on annotations.
---
## Error Handling
- Use standard JSON-RPC error codes
- Report tool errors within result objects (not protocol-level errors)
- Provide helpful, specific error messages with suggested next steps
- Don't expose internal implementation details
- Clean up resources properly on errors
Example error handling:
```typescript
try {
const result = performOperation();
return { content: [{ type: "text", text: result }] };
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error: ${error.message}. Try using filter='active_only' to reduce results.`
}]
};
}
```
---
## Testing Requirements
Comprehensive testing should cover:
- **Functional testing**: Verify correct execution with valid/invalid inputs
- **Integration testing**: Test interaction with external systems
- **Security testing**: Validate auth, input sanitization, rate limiting
- **Performance testing**: Check behavior under load, timeouts
- **Error handling**: Ensure proper error reporting and cleanup
---
## Documentation Requirements
- Provide clear documentation of all tools and capabilities
- Include working examples (at least 3 per major feature)
- Document security considerations
- Specify required permissions and access levels
- Document rate limits and performance characteristics

View File

@@ -0,0 +1,970 @@
# Node/TypeScript MCP Server Implementation Guide
## Overview
This document provides Node/TypeScript-specific best practices and examples for implementing MCP servers using the MCP TypeScript SDK. It covers project structure, server setup, tool registration patterns, input validation with Zod, error handling, and complete working examples.
---
## Quick Reference
### Key Imports
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.ts";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.ts";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.ts";
import express from "express";
import { z } from "zod";
```
### Server Initialization
```typescript
const server = new McpServer({
name: "service-mcp-server",
version: "1.0.0"
});
```
### Tool Registration Pattern
```typescript
server.registerTool(
"tool_name",
{
title: "Tool Display Name",
description: "What the tool does",
inputSchema: { param: z.string() },
outputSchema: { result: z.string() }
},
async ({ param }) => {
const output = { result: `Processed: ${param}` };
return {
content: [{ type: "text", text: JSON.stringify(output) }],
structuredContent: output // Modern pattern for structured data
};
}
);
```
---
## MCP TypeScript SDK
The official MCP TypeScript SDK provides:
- `McpServer` class for server initialization
- `registerTool` method for tool registration
- Zod schema integration for runtime input validation
- Type-safe tool handler implementations
**IMPORTANT - Use Modern APIs Only:**
- **DO use**: `server.registerTool()`, `server.registerResource()`, `server.registerPrompt()`
- **DO NOT use**: Old deprecated APIs such as `server.tool()`, `server.setRequestHandler(ListToolsRequestSchema, ...)`, or manual handler registration
- The `register*` methods provide better type safety, automatic schema handling, and are the recommended approach
See the MCP SDK documentation in the references for complete details.
## Server Naming Convention
Node/TypeScript MCP servers must follow this naming pattern:
- **Format**: `{service}-mcp-server` (lowercase with hyphens)
- **Examples**: `github-mcp-server`, `jira-mcp-server`, `stripe-mcp-server`
The name should be:
- General (not tied to specific features)
- Descriptive of the service/API being integrated
- Easy to infer from the task description
- Without version numbers or dates
## Project Structure
Create the following structure for Node/TypeScript MCP servers:
```
{service}-mcp-server/
├── package.json
├── tsconfig.json
├── README.md
├── src/
│ ├── index.ts # Main entry point with McpServer initialization
│ ├── types.ts # TypeScript type definitions and interfaces
│ ├── tools/ # Tool implementations (one file per domain)
│ ├── services/ # API clients and shared utilities
│ ├── schemas/ # Zod validation schemas
│ └── constants.ts # Shared constants (API_URL, CHARACTER_LIMIT, etc.)
└── dist/ # Built TypeScript files (entry point: dist/index.ts)
```
## Tool Implementation
### Tool Naming
Use snake_case for tool names (e.g., "search_users", "create_project", "get_channel_info") with clear, action-oriented names.
**Avoid Naming Conflicts**: Include the service context to prevent overlaps:
- Use "slack_send_message" instead of just "send_message"
- Use "github_create_issue" instead of just "create_issue"
- Use "asana_list_tasks" instead of just "list_tasks"
### Tool Structure
Tools are registered using the `registerTool` method with the following requirements:
- Use Zod schemas for runtime input validation and type safety
- The `description` field must be explicitly provided - JSDoc comments are NOT automatically extracted
- Explicitly provide `title`, `description`, `inputSchema`, and `annotations`
- The `inputSchema` must be a Zod schema object (not a JSON schema)
- Type all parameters and return values explicitly
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.ts";
import { z } from "zod";
const server = new McpServer({
name: "example-mcp",
version: "1.0.0"
});
// Zod schema for input validation
const UserSearchInputSchema = z.object({
query: z.string()
.min(2, "Query must be at least 2 characters")
.max(200, "Query must not exceed 200 characters")
.describe("Search string to match against names/emails"),
limit: z.number()
.int()
.min(1)
.max(100)
.default(20)
.describe("Maximum results to return"),
offset: z.number()
.int()
.min(0)
.default(0)
.describe("Number of results to skip for pagination"),
response_format: z.nativeEnum(ResponseFormat)
.default(ResponseFormat.MARKDOWN)
.describe("Output format: 'markdown' for human-readable or 'json' for machine-readable")
}).strict();
// Type definition from Zod schema
type UserSearchInput = z.infer<typeof UserSearchInputSchema>;
server.registerTool(
"example_search_users",
{
title: "Search Example Users",
description: `Search for users in the Example system by name, email, or team.
This tool searches across all user profiles in the Example platform, supporting partial matches and various search filters. It does NOT create or modify users, only searches existing ones.
Args:
- query (string): Search string to match against names/emails
- limit (number): Maximum results to return, between 1-100 (default: 20)
- offset (number): Number of results to skip for pagination (default: 0)
- response_format ('markdown' | 'json'): Output format (default: 'markdown')
Returns:
For JSON format: Structured data with schema:
{
"total": number, // Total number of matches found
"count": number, // Number of results in this response
"offset": number, // Current pagination offset
"users": [
{
"id": string, // User ID (e.g., "U123456789")
"name": string, // Full name (e.g., "John Doe")
"email": string, // Email address
"team": string, // Team name (optional)
"active": boolean // Whether user is active
}
],
"has_more": boolean, // Whether more results are available
"next_offset": number // Offset for next page (if has_more is true)
}
Examples:
- Use when: "Find all marketing team members" -> params with query="team:marketing"
- Use when: "Search for John's account" -> params with query="john"
- Don't use when: You need to create a user (use example_create_user instead)
Error Handling:
- Returns "Error: Rate limit exceeded" if too many requests (429 status)
- Returns "No users found matching '<query>'" if search returns empty`,
inputSchema: UserSearchInputSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params: UserSearchInput) => {
try {
// Input validation is handled by Zod schema
// Make API request using validated parameters
const data = await makeApiRequest<any>(
"users/search",
"GET",
undefined,
{
q: params.query,
limit: params.limit,
offset: params.offset
}
);
const users = data.users || [];
const total = data.total || 0;
if (!users.length) {
return {
content: [{
type: "text",
text: `No users found matching '${params.query}'`
}]
};
}
// Prepare structured output
const output = {
total,
count: users.length,
offset: params.offset,
users: users.map((user: any) => ({
id: user.id,
name: user.name,
email: user.email,
...(user.team ? { team: user.team } : {}),
active: user.active ?? true
})),
has_more: total > params.offset + users.length,
...(total > params.offset + users.length ? {
next_offset: params.offset + users.length
} : {})
};
// Format text representation based on requested format
let textContent: string;
if (params.response_format === ResponseFormat.MARKDOWN) {
const lines = [`# User Search Results: '${params.query}'`, "",
`Found ${total} users (showing ${users.length})`, ""];
for (const user of users) {
lines.push(`## ${user.name} (${user.id})`);
lines.push(`- **Email**: ${user.email}`);
if (user.team) lines.push(`- **Team**: ${user.team}`);
lines.push("");
}
textContent = lines.join("\n");
} else {
textContent = JSON.stringify(output, null, 2);
}
return {
content: [{ type: "text", text: textContent }],
structuredContent: output // Modern pattern for structured data
};
} catch (error) {
return {
content: [{
type: "text",
text: handleApiError(error)
}]
};
}
}
);
```
## Zod Schemas for Input Validation
Zod provides runtime type validation:
```typescript
import { z } from "zod";
// Basic schema with validation
const CreateUserSchema = z.object({
name: z.string()
.min(1, "Name is required")
.max(100, "Name must not exceed 100 characters"),
email: z.string()
.email("Invalid email format"),
age: z.number()
.int("Age must be a whole number")
.min(0, "Age cannot be negative")
.max(150, "Age cannot be greater than 150")
}).strict(); // Use .strict() to forbid extra fields
// Enums
enum ResponseFormat {
MARKDOWN = "markdown",
JSON = "json"
}
const SearchSchema = z.object({
response_format: z.nativeEnum(ResponseFormat)
.default(ResponseFormat.MARKDOWN)
.describe("Output format")
});
// Optional fields with defaults
const PaginationSchema = z.object({
limit: z.number()
.int()
.min(1)
.max(100)
.default(20)
.describe("Maximum results to return"),
offset: z.number()
.int()
.min(0)
.default(0)
.describe("Number of results to skip")
});
```
## Response Format Options
Support multiple output formats for flexibility:
```typescript
enum ResponseFormat {
MARKDOWN = "markdown",
JSON = "json"
}
const inputSchema = z.object({
query: z.string(),
response_format: z.nativeEnum(ResponseFormat)
.default(ResponseFormat.MARKDOWN)
.describe("Output format: 'markdown' for human-readable or 'json' for machine-readable")
});
```
**Markdown format**:
- Use headers, lists, and formatting for clarity
- Convert timestamps to human-readable format
- Show display names with IDs in parentheses
- Omit verbose metadata
- Group related information logically
**JSON format**:
- Return complete, structured data suitable for programmatic processing
- Include all available fields and metadata
- Use consistent field names and types
## Pagination Implementation
For tools that list resources:
```typescript
const ListSchema = z.object({
limit: z.number().int().min(1).max(100).default(20),
offset: z.number().int().min(0).default(0)
});
async function listItems(params: z.infer<typeof ListSchema>) {
const data = await apiRequest(params.limit, params.offset);
const response = {
total: data.total,
count: data.items.length,
offset: params.offset,
items: data.items,
has_more: data.total > params.offset + data.items.length,
next_offset: data.total > params.offset + data.items.length
? params.offset + data.items.length
: undefined
};
return JSON.stringify(response, null, 2);
}
```
## Character Limits and Truncation
Add a CHARACTER_LIMIT constant to prevent overwhelming responses:
```typescript
// At module level in constants.ts
export const CHARACTER_LIMIT = 25000; // Maximum response size in characters
async function searchTool(params: SearchInput) {
let result = generateResponse(data);
// Check character limit and truncate if needed
if (result.length > CHARACTER_LIMIT) {
const truncatedData = data.slice(0, Math.max(1, data.length / 2));
response.data = truncatedData;
response.truncated = true;
response.truncation_message =
`Response truncated from ${data.length} to ${truncatedData.length} items. ` +
`Use 'offset' parameter or add filters to see more results.`;
result = JSON.stringify(response, null, 2);
}
return result;
}
```
## Error Handling
Provide clear, actionable error messages:
```typescript
import axios, { AxiosError } from "axios";
function handleApiError(error: unknown): string {
if (error instanceof AxiosError) {
if (error.response) {
switch (error.response.status) {
case 404:
return "Error: Resource not found. Please check the ID is correct.";
case 403:
return "Error: Permission denied. You don't have access to this resource.";
case 429:
return "Error: Rate limit exceeded. Please wait before making more requests.";
default:
return `Error: API request failed with status ${error.response.status}`;
}
} else if (error.code === "ECONNABORTED") {
return "Error: Request timed out. Please try again.";
}
}
return `Error: Unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`;
}
```
## Shared Utilities
Extract common functionality into reusable functions:
```typescript
// Shared API request function
async function makeApiRequest<T>(
endpoint: string,
method: "GET" | "POST" | "PUT" | "DELETE" = "GET",
data?: any,
params?: any
): Promise<T> {
try {
const response = await axios({
method,
url: `${API_BASE_URL}/${endpoint}`,
data,
params,
timeout: 30000,
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
});
return response.data;
} catch (error) {
throw error;
}
}
```
## Async/Await Best Practices
Always use async/await for network requests and I/O operations:
```typescript
// Good: Async network request
async function fetchData(resourceId: string): Promise<ResourceData> {
const response = await axios.get(`${API_URL}/resource/${resourceId}`);
return response.data;
}
// Bad: Promise chains
function fetchData(resourceId: string): Promise<ResourceData> {
return axios.get(`${API_URL}/resource/${resourceId}`)
.then(response => response.data); // Harder to read and maintain
}
```
## TypeScript Best Practices
1. **Use Strict TypeScript**: Enable strict mode in tsconfig.json
2. **Define Interfaces**: Create clear interface definitions for all data structures
3. **Avoid `any`**: Use proper types or `unknown` instead of `any`
4. **Zod for Runtime Validation**: Use Zod schemas to validate external data
5. **Type Guards**: Create type guard functions for complex type checking
6. **Error Handling**: Always use try-catch with proper error type checking
7. **Null Safety**: Use optional chaining (`?.`) and nullish coalescing (`??`)
```typescript
// Good: Type-safe with Zod and interfaces
interface UserResponse {
id: string;
name: string;
email: string;
team?: string;
active: boolean;
}
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
team: z.string().optional(),
active: z.boolean()
});
type User = z.infer<typeof UserSchema>;
async function getUser(id: string): Promise<User> {
const data = await apiCall(`/users/${id}`);
return UserSchema.parse(data); // Runtime validation
}
// Bad: Using any
async function getUser(id: string): Promise<any> {
return await apiCall(`/users/${id}`); // No type safety
}
```
## Package Configuration
### package.json
```json
{
"name": "{service}-mcp-server",
"version": "1.0.0",
"description": "MCP server for {Service} API integration",
"type": "module",
"main": "dist/index.ts",
"scripts": {
"start": "node dist/index.ts",
"dev": "tsx watch src/index.ts",
"build": "tsc",
"clean": "rm -rf dist"
},
"engines": {
"node": ">=18"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.6.1",
"axios": "^1.7.9",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^22.10.0",
"tsx": "^4.19.2",
"typescript": "^5.7.2"
}
}
```
### tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
## Complete Example
```typescript
#!/usr/bin/env node
/**
* MCP Server for Example Service.
*
* This server provides tools to interact with Example API, including user search,
* project management, and data export capabilities.
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.ts";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.ts";
import { z } from "zod";
import axios, { AxiosError } from "axios";
// Constants
const API_BASE_URL = "https://api.example.com/v1";
const CHARACTER_LIMIT = 25000;
// Enums
enum ResponseFormat {
MARKDOWN = "markdown",
JSON = "json"
}
// Zod schemas
const UserSearchInputSchema = z.object({
query: z.string()
.min(2, "Query must be at least 2 characters")
.max(200, "Query must not exceed 200 characters")
.describe("Search string to match against names/emails"),
limit: z.number()
.int()
.min(1)
.max(100)
.default(20)
.describe("Maximum results to return"),
offset: z.number()
.int()
.min(0)
.default(0)
.describe("Number of results to skip for pagination"),
response_format: z.nativeEnum(ResponseFormat)
.default(ResponseFormat.MARKDOWN)
.describe("Output format: 'markdown' for human-readable or 'json' for machine-readable")
}).strict();
type UserSearchInput = z.infer<typeof UserSearchInputSchema>;
// Shared utility functions
async function makeApiRequest<T>(
endpoint: string,
method: "GET" | "POST" | "PUT" | "DELETE" = "GET",
data?: any,
params?: any
): Promise<T> {
try {
const response = await axios({
method,
url: `${API_BASE_URL}/${endpoint}`,
data,
params,
timeout: 30000,
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
});
return response.data;
} catch (error) {
throw error;
}
}
function handleApiError(error: unknown): string {
if (error instanceof AxiosError) {
if (error.response) {
switch (error.response.status) {
case 404:
return "Error: Resource not found. Please check the ID is correct.";
case 403:
return "Error: Permission denied. You don't have access to this resource.";
case 429:
return "Error: Rate limit exceeded. Please wait before making more requests.";
default:
return `Error: API request failed with status ${error.response.status}`;
}
} else if (error.code === "ECONNABORTED") {
return "Error: Request timed out. Please try again.";
}
}
return `Error: Unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`;
}
// Create MCP server instance
const server = new McpServer({
name: "example-mcp",
version: "1.0.0"
});
// Register tools
server.registerTool(
"example_search_users",
{
title: "Search Example Users",
description: `[Full description as shown above]`,
inputSchema: UserSearchInputSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
async (params: UserSearchInput) => {
// Implementation as shown above
}
);
// Main function
// For stdio (local):
async function runStdio() {
if (!process.env.EXAMPLE_API_KEY) {
console.error("ERROR: EXAMPLE_API_KEY environment variable is required");
process.exit(1);
}
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server running via stdio");
}
// For streamable HTTP (remote):
async function runHTTP() {
if (!process.env.EXAMPLE_API_KEY) {
console.error("ERROR: EXAMPLE_API_KEY environment variable is required");
process.exit(1);
}
const app = express();
app.use(express.json());
app.post('/mcp', async (req, res) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true
});
res.on('close', () => transport.close());
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
const port = parseInt(process.env.PORT || '3000');
app.listen(port, () => {
console.error(`MCP server running on http://localhost:${port}/mcp`);
});
}
// Choose transport based on environment
const transport = process.env.TRANSPORT || 'stdio';
if (transport === 'http') {
runHTTP().catch(error => {
console.error("Server error:", error);
process.exit(1);
});
} else {
runStdio().catch(error => {
console.error("Server error:", error);
process.exit(1);
});
}
```
---
## Advanced MCP Features
### Resource Registration
Expose data as resources for efficient, URI-based access:
```typescript
import { ResourceTemplate } from "@modelcontextprotocol/sdk/types.ts";
// Register a resource with URI template
server.registerResource(
{
uri: "file://documents/{name}",
name: "Document Resource",
description: "Access documents by name",
mimeType: "text/plain"
},
async (uri: string) => {
// Extract parameter from URI
const match = uri.match(/^file:\/\/documents\/(.+)$/);
if (!match) {
throw new Error("Invalid URI format");
}
const documentName = match[1];
const content = await loadDocument(documentName);
return {
contents: [{
uri,
mimeType: "text/plain",
text: content
}]
};
}
);
// List available resources dynamically
server.registerResourceList(async () => {
const documents = await getAvailableDocuments();
return {
resources: documents.map(doc => ({
uri: `file://documents/${doc.name}`,
name: doc.name,
mimeType: "text/plain",
description: doc.description
}))
};
});
```
**When to use Resources vs Tools:**
- **Resources**: For data access with simple URI-based parameters
- **Tools**: For complex operations requiring validation and business logic
- **Resources**: When data is relatively static or template-based
- **Tools**: When operations have side effects or complex workflows
### Transport Options
The TypeScript SDK supports two main transport mechanisms:
#### Streamable HTTP (Recommended for Remote Servers)
```typescript
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.ts";
import express from "express";
const app = express();
app.use(express.json());
app.post('/mcp', async (req, res) => {
// Create new transport for each request (stateless, prevents request ID collisions)
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true
});
res.on('close', () => transport.close());
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
app.listen(3000);
```
#### stdio (For Local Integrations)
```typescript
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.ts";
const transport = new StdioServerTransport();
await server.connect(transport);
```
**Transport selection:**
- **Streamable HTTP**: Web services, remote access, multiple clients
- **stdio**: Command-line tools, local development, subprocess integration
### Notification Support
Notify clients when server state changes:
```typescript
// Notify when tools list changes
server.notification({
method: "notifications/tools/list_changed"
});
// Notify when resources change
server.notification({
method: "notifications/resources/list_changed"
});
```
Use notifications sparingly - only when server capabilities genuinely change.
---
## Code Best Practices
### Code Composability and Reusability
Your implementation MUST prioritize composability and code reuse:
1. **Extract Common Functionality**:
- Create reusable helper functions for operations used across multiple tools
- Build shared API clients for HTTP requests instead of duplicating code
- Centralize error handling logic in utility functions
- Extract business logic into dedicated functions that can be composed
- Extract shared markdown or JSON field selection & formatting functionality
2. **Avoid Duplication**:
- NEVER copy-paste similar code between tools
- If you find yourself writing similar logic twice, extract it into a function
- Common operations like pagination, filtering, field selection, and formatting should be shared
- Authentication/authorization logic should be centralized
## Building and Running
Always build your TypeScript code before running:
```bash
# Build the project
npm run build
# Run the server
npm start
# Development with auto-reload
npm run dev
```
Always ensure `npm run build` completes successfully before considering the implementation complete.
## Quality Checklist
Before finalizing your Node/TypeScript MCP server implementation, ensure:
### Strategic Design
- [ ] Tools enable complete workflows, not just API endpoint wrappers
- [ ] Tool names reflect natural task subdivisions
- [ ] Response formats optimize for agent context efficiency
- [ ] Human-readable identifiers used where appropriate
- [ ] Error messages guide agents toward correct usage
### Implementation Quality
- [ ] FOCUSED IMPLEMENTATION: Most important and valuable tools implemented
- [ ] All tools registered using `registerTool` with complete configuration
- [ ] All tools include `title`, `description`, `inputSchema`, and `annotations`
- [ ] Annotations correctly set (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
- [ ] All tools use Zod schemas for runtime input validation with `.strict()` enforcement
- [ ] All Zod schemas have proper constraints and descriptive error messages
- [ ] All tools have comprehensive descriptions with explicit input/output types
- [ ] Descriptions include return value examples and complete schema documentation
- [ ] Error messages are clear, actionable, and educational
### TypeScript Quality
- [ ] TypeScript interfaces are defined for all data structures
- [ ] Strict TypeScript is enabled in tsconfig.json
- [ ] No use of `any` type - use `unknown` or proper types instead
- [ ] All async functions have explicit Promise<T> return types
- [ ] Error handling uses proper type guards (e.g., `axios.isAxiosError`, `z.ZodError`)
### Advanced Features (where applicable)
- [ ] Resources registered for appropriate data endpoints
- [ ] Appropriate transport configured (stdio or streamable HTTP)
- [ ] Notifications implemented for dynamic server capabilities
- [ ] Type-safe with SDK interfaces
### Project Configuration
- [ ] Package.json includes all necessary dependencies
- [ ] Build script produces working TypeScript in dist/ directory
- [ ] Main entry point is properly configured as dist/index.ts
- [ ] Server name follows format: `{service}-mcp-server`
- [ ] tsconfig.json properly configured with strict mode
### Code Quality
- [ ] Pagination is properly implemented where applicable
- [ ] Large responses check CHARACTER_LIMIT constant and truncate with clear messages
- [ ] Filtering options are provided for potentially large result sets
- [ ] All network operations handle timeouts and connection errors gracefully
- [ ] Common functionality is extracted into reusable functions
- [ ] Return types are consistent across similar operations
### Testing and Build
- [ ] `npm run build` completes successfully without errors
- [ ] dist/index.ts created and executable
- [ ] Server runs: `node dist/index.ts --help`
- [ ] All imports resolve correctly
- [ ] Sample tool calls work as expected

View File

@@ -0,0 +1,59 @@
---
name: message-queue
version: 1.0.0
description: "配置 Hyperf AsyncQueue + Redis 实现后台任务处理。当需要异步任务、消息队列或延迟队列时使用。含重试策略和事件驱动模式。"
---
# Hyperf Async Queue (Message Queue)
## 触发条件
用户需要异步处理任务:通知发送、数据同步、超时检测、日志记录、批量操作。
## 执行流程
### Phase 0: 加载规范
读取 `.cursor/rules/013-backend.mdc``016-swoole.mdc`,提取 Job 命名、DI、协程安全、连接池。
### Phase 1: 队列配置
`config/autoload/async_queue.php`Redis 驱动、channel、retry_seconds 指数退避、handle_timeout、processes、concurrent。可配置 default 与 notification 等多队列。
### Phase 2: Job 类
继承 `Hyperf\AsyncQueue\Job``maxAttempts`、幂等检查、`handle()` 内 try-catch 记录日志后 re-throw 触发重试。
### Phase 3: 投递任务
QueueService 封装 `dispatch``dispatchNotification``dispatchDelayed`。从 DriverFactory 获取 driver 后 push。
### Phase 4: 使用场景
通知发送、超时检测(延迟 30 分钟、批量处理chunk 后逐批 dispatch
### Phase 5: 事件驱动
Event → Listener → QueueService.dispatch。Service 更新数据后 event() 触发Listener 内异步投递多个 Job。
### Phase 6: 监控
队列 health check 命令Redis 统计 waiting/delayed/failed/timeoutfailed > 100 告警。
完整实现见 **Tier 3**
## 验证
1. [ ] 配置使用环境变量
2. [ ] Job 幂等
3. [ ] maxAttempts、retry_seconds 合理
4. [ ] handle_timeout 大于 Job 最大执行时间
5. [ ] 关键 Job 有错误日志
6. [ ] 延迟队列用于超时检测
7. [ ] 消费进程在 supervisor 注册
## Tier 3 深度参考
| 文件 | 内容 |
|------|------|
| `references/queue-implementation.md` | 配置、Job、投递、事件驱动、监控完整代码 |

View File

@@ -0,0 +1,46 @@
# Message Queue — 实现细节
> 主流程见 SKILL.md本文档为配置、Job、投递、事件驱动、监控的完整代码。
## 队列配置
```php
// config/autoload/async_queue.php
return [
'default' => [
'driver' => Hyperf\AsyncQueue\Driver\RedisDriver::class,
'redis' => ['pool' => 'default'],
'channel' => env('QUEUE_CHANNEL', '{queue}'),
'timeout' => 2,
'retry_seconds' => [1, 5, 10, 30, 60],
'handle_timeout' => 60,
'processes' => 1,
'concurrent' => ['limit' => 10],
],
'notification' => [ /* 独立队列,更高优先级 */ ],
];
```
## Job 类模板
```php
class {{JobName}}Job extends Job
{
protected int $maxAttempts = 3;
public function __construct(protected readonly int $resourceId, protected readonly array $payload = []) {}
public function handle(): void {
$resource = $this->getResource();
if (!$resource || $this->isAlreadyProcessed($resource)) return;
try { $this->process($resource); }
catch (\Throwable $e) { logger()->error(...); throw $e; }
}
}
```
## 投递与事件驱动
QueueService`dispatch($job, $delay)``dispatchNotification($job)``dispatchDelayed($job, $delaySeconds)`。事件驱动Event → Listener → QueueService->dispatch。示例OrderStatusChanged → 异步通知、同步外部、更新统计缓存debounce 5s
## 监控命令
Redis `{queue}:waiting``{queue}:delayed``{queue}:failed``{queue}:timeout`。failed > 100 告警。

View File

@@ -0,0 +1,386 @@
---
name: module-scaffold
version: 2.1.0
description: "生成 Hyperf 模块化业务模块脚手架ConfigProvider/Controller/Service/Model。当需要新建业务模块、创建 module、添加模块化功能时使用。基于 ModuleLoader 自动发现 + ConfigProvider 机制,无需修改 composer.json。"
requires: [hyperf-service]
---
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-module-scaffold.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Hyperf Module Scaffold
## 触发条件
用户要求创建新的业务模块、在 `modules/` 下新建功能模块、或提到"模块化"、"新模块"、"module"。
## 前置条件
项目已完成模块化架构初始化:
- `Case-Database-Backend/modules/` 目录存在
- `app/Support/ModuleLoader.php` 已存在并注册到 `composer.json` `autoload.files`
- `config/autoload/annotations.php` 已包含 `BASE_PATH . '/modules'` 扫描路径
若未初始化,先引导用户完成架构搭建。
## 工作原理
`ModuleLoader.php``vendor/autoload.php` 加载时Hyperf DI 容器初始化之前)自动执行:
1. 扫描 `modules/*/composer.json`
2. 读取每个模块的 `extra.hyperf.config`
3. 通过反射注入 `Hyperf\Support\Composer::$extra`
4. 同时注册模块的 PSR-4 命名空间到 Composer ClassLoader
这使得 `ProviderConfig::load()` 能发现所有模块的 ConfigProvider**无需将模块添加到主项目 `composer.json``require` 中**。
## 执行流程
### 1. 确认模块规格
| 字段 | 必填 | 默认值 | 说明 |
|------|------|--------|------|
| 模块名称 | ✅ | — | PascalCase`Order``UserCenter` |
| 模块描述 | ❌ | 推断 | 一句话说明模块职责 |
| 接口类型 | ❌ | `api` | `api`(用户端)/ `admin`(管理端)/ `both`(两者都有) |
| 需要 Service | ❌ | true | 业务逻辑层 |
| 需要 Model | ❌ | false | 数据模型 |
| 需要 Repository | ❌ | false | 复杂查询时 |
| 需要 Middleware | ❌ | false | 模块级中间件 |
| 需要 Request | ❌ | false | 表单验证 |
| 路由前缀 | ❌ | 见下方规则 | 自动推断,也可手动指定 |
### 2. Controller 与 Request 目录结构规则(核心)
根据接口类型Controller 和 Request **均放入对应子目录**,命名空间随之变更:
| 接口类型 | 路由前缀 | Controller 目录 | Request 目录 | PHP 命名空间Controller |
|----------|----------|-----------------|--------------|--------------------------|
| 用户端 API`/api/*` | `/api/{module-kebab}` | `Http/Controller/Api/` | `Http/Request/Api/` | `Modules\{Name}\Http\Controller\Api` |
| 管理端 API`/admin/*` | `/admin/{module-kebab}` | `Http/Controller/Admin/` | `Http/Request/Admin/` | `Modules\{Name}\Http\Controller\Admin` |
| 两者都有(`both` | 各自前缀 | 两个 Controller 子目录 | 两个 Request 子目录 | 各自命名空间 |
**判断规则**
- 路由前缀包含 `/api/` → Controller 放 `Controller/Api/`Request 放 `Request/Api/`
- 路由前缀包含 `/admin/` → Controller 放 `Controller/Admin/`Request 放 `Request/Admin/`
- 用户未指定接口类型时,优先询问,或根据模块职责推断
- 若模块同时有用户端和管理端接口,两组子目录都创建
- **类名不需要加接口类型前缀**,子目录本身已区分,类名保持简洁
### 3. 生成目录结构
**仅有用户端 API最常见**
```
modules/{ModuleName}/
├── composer.json
└── src/
├── ConfigProvider.php
├── Http/
│ ├── Controller/
│ │ └── Api/ # 用户端控制器
│ │ └── {Name}Controller.php
│ ├── Request/
│ │ └── Api/ # 用户端请求验证
│ │ └── {Name}Request.php
│ └── Middleware/ # 模块中间件(按需)
├── Service/
├── Model/ # 按需
├── Repository/ # 按需
├── Event/ # 按需
├── Listener/ # 按需
└── Constants/ # 按需
```
**仅有管理端 API**
```
modules/{ModuleName}/
└── src/
├── Http/
│ ├── Controller/
│ │ └── Admin/ # 管理端控制器
│ │ └── {Name}Controller.php
│ ├── Request/
│ │ └── Admin/ # 管理端请求验证
│ │ └── {Name}Request.php
...
```
**同时有用户端 + 管理端both**
```
modules/{ModuleName}/
└── src/
├── Http/
│ ├── Controller/
│ │ ├── Api/ # 用户端控制器
│ │ │ └── {Name}Controller.php
│ │ └── Admin/ # 管理端控制器
│ │ └── {Name}Controller.php
│ ├── Request/
│ │ ├── Api/ # 用户端请求验证
│ │ │ └── {Name}Request.php
│ │ └── Admin/ # 管理端请求验证
│ │ └── {Name}Request.php
...
```
### 4. 生成 composer.json
```json
{
"name": "modules/{module-kebab}",
"description": "{模块描述}",
"type": "library",
"autoload": {
"psr-4": {
"Modules\\{ModuleName}\\": "src/"
}
},
"extra": {
"hyperf": {
"config": "Modules\\{ModuleName}\\ConfigProvider"
}
}
}
```
**命名规则**
- Composer 包名:`modules/{kebab-case}`,如 `modules/user-center`
- PHP 命名空间:`Modules\{PascalCase}`,如 `Modules\UserCenter`
如模块有独立的第三方依赖,可在模块 `composer.json``require` 中声明,`wikimedia/composer-merge-plugin` 会在下次 `composer update` 时合并到主项目。
### 5. 生成 ConfigProvider.php
```php
<?php
declare(strict_types=1);
namespace Modules\{ModuleName};
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
// Interface::class => Implementation::class,
],
'annotations' => [
'scan' => [
'paths' => [
__DIR__,
],
],
],
'publish' => [],
];
}
}
```
**ConfigProvider 职责**
- `dependencies`:注册模块的 DI 绑定(接口 → 实现)
- `annotations.scan.paths`:注册注解扫描路径(`__DIR__` 指向 `src/`
- `publish`:可发布的配置文件(可选)
### 6. 生成 Controller 模板
**用户端 API Controller**(放在 `Http/Controller/Api/`
```php
<?php
declare(strict_types=1);
namespace Modules\{ModuleName}\Http\Controller\Api;
use App\Controller\AbstractController;
use App\Support\ResponseTrait;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\GetMapping;
use Modules\{ModuleName}\Service\{ModuleName}Service;
use Psr\Http\Message\ResponseInterface;
#[Controller(prefix: '/api/{module-kebab}')]
class {ModuleName}Controller extends AbstractController
{
use ResponseTrait;
#[Inject]
protected {ModuleName}Service ${moduleName}Service;
#[GetMapping(path: '')]
public function index(): ResponseInterface
{
return $this->success([]);
}
}
```
**管理端 API Controller**(放在 `Http/Controller/Admin/`
```php
<?php
declare(strict_types=1);
namespace Modules\{ModuleName}\Http\Controller\Admin;
use App\Controller\AbstractController;
use App\Middleware\JwtAuthMiddleware;
use App\Support\ResponseTrait;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\GetMapping;
use Hyperf\HttpServer\Annotation\Middleware;
use Modules\{ModuleName}\Service\{ModuleName}Service;
use Psr\Http\Message\ResponseInterface;
#[Controller(prefix: '/admin/{module-kebab}')]
#[Middleware(JwtAuthMiddleware::class)]
class {ModuleName}Controller extends AbstractController
{
use ResponseTrait;
#[Inject]
protected {ModuleName}Service ${moduleName}Service;
#[GetMapping(path: '')]
public function index(): ResponseInterface
{
return $this->success([]);
}
}
```
**关键区别**
- `Api/` 下:前缀 `/api/`,命名空间含 `\Api\`
- `Admin/` 下:前缀 `/admin/`,命名空间含 `\Admin\`,自动加 `JwtAuthMiddleware`
- **两者类名相同**(均为 `{Name}Controller`),由命名空间区分,无需加接口类型前缀
### 7. 生成 Request 模板
**用户端 Request**(放在 `Http/Request/Api/`
```php
<?php
declare(strict_types=1);
namespace Modules\{ModuleName}\Http\Request\Api;
use Hyperf\Validation\Request\FormRequest;
class {Name}Request extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
// 'field' => 'required|string|max:255',
];
}
public function messages(): array
{
return [];
}
}
```
**管理端 Request**(放在 `Http/Request/Admin/`
```php
<?php
declare(strict_types=1);
namespace Modules\{ModuleName}\Http\Request\Admin;
use Hyperf\Validation\Request\FormRequest;
class {Name}Request extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
// 'field' => 'required|string|max:255',
];
}
public function messages(): array
{
return [];
}
}
```
**关键区别**
- `Api/` 下:命名空间含 `\Request\Api\`,类名 `{Name}Request`
- `Admin/` 下:命名空间含 `\Request\Admin\`,类名 `{Name}Request`
- **两者类名相同**,由命名空间区分,无需加接口类型前缀
- Controller 中 `use` 路径需对应各自子命名空间
### 8. 验证模块自动发现
创建文件后**无需任何 composer 操作**,直接验证:
```bash
docker exec hyperf-skeleton php -r "
define('BASE_PATH', '/opt/www');
require '/opt/www/vendor/autoload.php';
\$extra = Hyperf\Support\Composer::getMergedExtra('hyperf');
echo in_array('Modules\\{ModuleName}\\ConfigProvider', \$extra['config'] ?? []) ? 'OK' : 'FAIL';
"
```
## 模块间依赖
若模块 A 依赖模块 B 的类:
- 通过 DI 注入模块 B 的 Service不直接依赖 Controller/Model
- 遵循依赖方向:上层模块 → 下层模块,禁止循环依赖
- ModuleLoader 的 glob 扫描天然支持多模块并存,无需额外配置
## 已有模块示例参考
当前项目 `modules/Auth/` 的实际结构(**参考标准**
```
modules/Auth/src/Http/Controller/
├── Api/ # 用户端接口(推荐新规范)
│ ├── AuthController.php # prefix: /api/auth
│ ├── CaptchaController.php # prefix: /api/auth/captcha
│ └── SmsController.php # prefix: /api/auth/sms
└── Admin/ # 管理端接口(推荐新规范)
└── AdminAuthController.php # prefix: /admin/auth
```
> 注:当前 Auth 模块尚未按此规范重构(所有 Controller 平铺在 `Controller/` 下),新模块应从一开始遵循子目录规范。
## 验证
1. [ ] 模块目录结构完整composer.json + src/ConfigProvider.php + Http/Controller/
2. [ ] **Controller 按接口类型放入正确子目录**`/api/*``Controller/Api/``/admin/*``Controller/Admin/`
3. [ ] Controller 命名空间包含正确子层级:`...\Http\Controller\Api\``...\Http\Controller\Admin\`
4. [ ] `Admin/` 下的 Controller 自动添加 `JwtAuthMiddleware`;类名与 `Api/` 下保持一致,无需加前缀
5. [ ] **Request 按接口类型放入正确子目录**`/api/*``Request/Api/``/admin/*``Request/Admin/`
6. [ ] Request 命名空间包含正确子层级:`...\Http\Request\Api\``...\Http\Request\Admin\`
7. [ ] Controller 中 `use` 的 Request 路径与子目录一致
8. [ ] composer.json 的 `name` 使用 `modules/{kebab-case}` 格式
9. [ ] namespace 使用 `Modules\{PascalCase}` 格式
10. [ ] ConfigProvider 注册了 `__DIR__` 注解扫描路径
11. [ ] `extra.hyperf.config` 指向正确的 ConfigProvider 类
12. [ ] 无需修改主项目 `composer.json`,模块被 ModuleLoader 自动发现
13. [ ] `ProviderConfig::load()` 能发现模块 ConfigProvider
14. [ ] `php -l` 所有 PHP 文件语法正确
15. [ ] Controller 路由前缀遵循 RESTful 命名kebab-case

View File

@@ -0,0 +1,216 @@
---
name: nginx-config
version: 2.0.0
description: "配置 Nginx 作为 Hyperf + Vue 3 的反向代理和静态文件服务。当需要 SSL、负载均衡或 Nginx 优化时使用。"
---
# 🔧 Nginx Config (Hyperf + Vue 3 SPA)
## 触发条件
用户需要配置 Nginx 作为 Hyperf API + Vue 3 SPA 前端的反向代理、SSL 终端、静态文件服务器或负载均衡器。
## Phase 0场景确认
| 场景 | 对应配置 |
|------|---------|
| 开发环境反向代理 | 基础 proxy_pass |
| 生产环境单机部署 | proxy_pass + SSL + 安全头 |
| 多实例负载均衡 | upstream + health check |
| 前端 SPA 静态资源 | try_files + 长期缓存 |
| WebSocket 支持 | upgrade + connection 头 |
| API 反向代理 | /api -> Hyperf 9501 |
---
## Phase 1基础架构配置 (Hyperf + Vue 3 SPA)
```nginx
# /etc/nginx/sites-available/myapp.conf
# ── 后端 Upstream ─────────────────────────
upstream hyperf_backend {
server 127.0.0.1:9501;
keepalive 32;
}
upstream hyperf_websocket {
server 127.0.0.1:9502;
}
# ── HTTP -> HTTPS 重定向 ──────────────────
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
# ── 主配置 ────────────────────────────────
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# ── SSL 证书 ──────────────────────────
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# ── 安全头 ────────────────────────────
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# ── 文件上传限制 ──────────────────────
client_max_body_size 20M;
# ── Gzip 压缩 ────────────────────────
gzip on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_vary on;
gzip_types
text/plain
text/css
application/json
application/typescript
text/xml
application/xml
image/svg+xml;
# ── Vue 3 SPA 前端 ───────────────────
root /var/www/myapp/Case-Database-Frontend-user/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# ── Vite 产物静态资源(带 hash 长期缓存)──
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
location /favicon.ico {
expires 30d;
access_log off;
}
# ── Hyperf API 反向代理 ──────────────
location /admin/ {
proxy_pass http://hyperf_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# ── WebSocket 代理 ───────────────────
location /ws {
proxy_pass http://hyperf_websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 3600s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
}
# ── 文件上传/下载 ────────────────────
location /admin/upload {
proxy_pass http://hyperf_backend;
client_max_body_size 50M;
proxy_request_buffering off;
}
# ── 拒绝隐藏文件 ────────────────────
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
```
---
## Phase 2负载均衡配置
```nginx
# 多 Hyperf 实例
upstream hyperf_backend {
least_conn;
server 10.0.0.1:9501 weight=3;
server 10.0.0.2:9501 weight=3;
server 10.0.0.3:9501 backup;
keepalive 64;
}
# 健康检查(需要 nginx-upstream-check 模块或商业版)
# 替代方案K8s Ingress + Pod 健康检查
```
---
## Phase 3Let's Encrypt SSL
```bash
# 安装 Certbot
sudo apt install certbot python3-certbot-nginx
# 自动获取证书
sudo certbot --nginx -d example.com -d www.example.com
# 自动续期Certbot 自动添加 cron
sudo certbot renew --dry-run
```
---
## Phase 4Docker Compose 中的 Nginx
```yaml
# docker-compose.yml 中 Nginx 服务
services:
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./Case-Database-Frontend-user/dist:/var/www/myapp/Case-Database-Frontend-user/dist:ro
- ./certbot/conf:/etc/letsencrypt:ro
depends_on:
- hyperf
restart: unless-stopped
```
---
## 验证
1. [ ] `nginx -t` 配置语法检查通过
2. [ ] HTTPS 证书有效
3. [ ] SPA 路由刷新正常 (try_files -> /index.html)
4. [ ] API 代理 `/admin/*` 正常
5. [ ] WebSocket `/ws` 连接正常
6. [ ] 静态资源 `/assets/*` 带 Cache-Control
7. [ ] 安全头通过 securityheaders.com 检查
8. [ ] Gzip 压缩生效

View File

@@ -0,0 +1,79 @@
---
name: performance-audit
version: 1.0.0
description: "分析并优化应用性能。当用户报告页面慢、加载卡或包体积大时使用。涵盖前端渲染、网络、打包和数据库四个维度。"
---
> ⚠️ 核心执行流程已在 `.cursor/rules/022-performance.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Performance Audit
## 触发条件
用户报告性能问题、请求性能优化或要求性能审计。
## 执行流程
### 1. 性能基线
收集当前数据:
```bash
# 前端打包分析 (Vite)
for dir in Case-Database-Frontend-user Case-Database-Frontend-admin; do
echo "=== $dir ==="
cd $dir && npm run build 2>&1 | tail -20
du -sh dist/
cd ..
done
# 后端状态
cd Case-Database-Backend && php bin/hyperf.php describe:routes | wc -l
```
### 2. 四维度审计
**前端渲染**
- Vue DevTools 分析不必要的重渲染
- 检查大列表是否缺少 `v-memo` / `shallowRef`
- 检查大列表是否使用虚拟化(`@tanstack/vue-virtual`
- 检查图片是否压缩WebP 格式 + CDN
- 管理端Element Plus 是否按需导入(用户端不使用 Element Plus
**网络请求**
- 是否有瀑布式请求(串行 → 并行)
- API 响应是否有 Redis 缓存策略
- Axios 是否配置请求/响应拦截
- 静态内容是否配置 Nginx 缓存
**打包体积**
- 是否有不必要的大依赖
- 是否使用 dynamic import 进行代码分割
- 是否 tree-shaking 有效
- 检查 barrel exports 是否导致全量导入
**数据库查询**
- N+1 查询检测
- 缺少索引的慢查询
- 未使用 `select` 限制字段
- 大数据集未分页
### 3. Core Web Vitals 目标
| 指标 | 目标 | 说明 |
|------|------|------|
| LCP | < 2.5s | Largest Contentful Paint |
| INP | < 200ms | Interaction to Next Paint |
| CLS | < 0.1 | Cumulative Layout Shift |
| FCP | < 1.8s | First Contentful Paint |
| TTFB | < 800ms | Time to First Byte |
### 4. 输出优化建议
按影响力排序,每个建议包含:预期收益 + 实施难度 + 具体代码。
## 验证
1. [ ] 优化前后有可量化对比
2. [ ] 未引入功能回归
3. [ ] Core Web Vitals 达标

View File

@@ -0,0 +1,87 @@
# 性能优化模式
## Vue 3 前端优化
```vue
<!-- 1. v-memo 防止不必要的重渲染 -->
<template>
<div v-memo="[item.id, item.name]">
{{ item.name }}
</div>
</template>
<!-- 2. computed 缓存计算结果 -->
<script setup>
const sorted = computed(() => [...items.value].sort(compareFn))
// 3. shallowRef — 大型对象避免深层响应
const heavyData = shallowRef(null)
// 4. 异步组件 — 代码分割
const HeavyChart = defineAsyncComponent(() => import('./HeavyChart.vue'))
// 5. 虚拟化 — 大列表
import { useVirtualizer } from '@tanstack/vue-virtual'
// 6. keep-alive — 页面缓存
// <keep-alive :include="cachedViews"><router-view /></keep-alive>
</script>
```
## Vite 构建优化
```typescript
// vite.config.ts
export default defineConfig({
build: {
target: 'es2015',
minify: 'terser',
terserOptions: {
compress: { drop_console: true, drop_debugger: true },
},
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
// 仅管理端:'element-plus': ['element-plus'],
'echarts': ['echarts'],
},
},
},
},
})
```
## Hyperf 后端优化
```php
// 1. 协程并发 — 并行无依赖 I/O
$parallel = new Parallel(10);
$parallel->add(fn() => $this->orderService->getStatistics($id));
$parallel->add(fn() => $this->paymentService->getPayments($id));
[$stats, $payments] = $parallel->wait();
// 2. 连接池调优
'pool' => [
'min_connections' => 5,
'max_connections' => 50,
'wait_timeout' => 3.0,
],
// 3. 缓存注解
#[Cacheable(prefix: 'user', ttl: 3600)]
public function getById(int $id): ?User { ... }
```
## 数据库优化
```sql
-- 复合索引:遵循最左前缀原则
CREATE INDEX idx_orders_status_created ON production_orders(status, created_at);
-- 覆盖索引:避免回表
CREATE INDEX idx_orders_cover ON production_orders(status, total_amount, paid_amount);
-- 游标分页(百万级数据)
SELECT * FROM orders WHERE id > ? ORDER BY id ASC LIMIT 20;
```

View File

@@ -0,0 +1,67 @@
---
name: redis-cache
version: 3.0.0
description: "为 Hyperf/Swoole 应用设计 Redis 缓存策略。当需要缓存、限流或 Session 存储时使用。涵盖 TTL 设计、缓存失效和高并发模式。"
---
# 🔴 Redis Cache (Hyperf + Swoole)
## 触发条件
用户需要为应用添加缓存层、会话存储、限流计数器或消息队列。
## Phase 0场景确认
| 场景 | 数据结构 | 说明 |
|------|---------|------|
| API 响应 | STRING | key-value + TTL |
| Session/Token | HASH | 多字段 |
| 排行榜 | ZSET | 范围查询 |
| 限流 | ZSET 滑动窗口 | 精准窗口 |
| 标签/权限 | SET | 集合运算 |
| 消息队列 | LIST/STREAM | FIFO |
| 分布式锁 | STRING + SET NX | 原子性 |
| WebSocket | HASH | fd→userId |
## Phase 1连接配置
`config/autoload/redis.php`default 与 cache 独立连接池,环境变量 REDIS_HOST/PORT/AUTH/DB/CACHE_DB。
## Phase 2缓存策略
Cache-Aside`withCache($key, $ttl, $fetcher)`fetcher 内查 DBsetex 加 jitter。invalidate/invalidatePattern。
CachedRepository装饰器包装 RepositorygetById/getPageList 走缓存,写后 invalidateAll。读写比 > 10:1 用装饰器,< 10:1 用 Service 内 CacheService热点数据用 Redis 原生。完整实现见 **Tier 3**
## Phase 3TTL 与 Key 规范
Profile 30m、列表 5m、配置 1h、JWT 2h、Session 24h。Key 格式:`namespace:entity:id:field`
## Phase 4分布式锁
withLock($lockKey, $ttlMs, $fn)SET NX PX释放用 Lua 原子校验 value 后 DEL。
## Phase 5穿透/击穿防护
穿透:空值缓存 NULL_PLACEHOLDER。击穿热点 key 用 withLock 互斥 + double-check。
## Phase 6监控
cache hit/miss 计数redis-cli INFO stats。
## 验证
1. [ ] 连接使用环境变量
2. [ ] Key 命名规范
3. [ ] 所有 Key 有 TTL
4. [ ] 失效与更新逻辑同步
5. [ ] 锁用 Lua 原子释放
6. [ ] 使用连接池
7. [ ] 百万级有穿透/击穿防护
8. [ ] CachedRepository 写后 invalidateAll
## Tier 3 深度参考
| 文件 | 内容 |
|------|------|
| `references/cache-implementation.md` | 连接、Cache-Aside、CachedRepository、TTL、锁、穿透/击穿完整代码 |

View File

@@ -0,0 +1,41 @@
# Redis Cache — 实现细节
> 主流程见 SKILL.md本文档为连接、Cache-Aside、CachedRepository、TTL、分布式锁、穿透/击穿防护的完整代码。
## 连接配置
```php
// config/autoload/redis.php
return [
'default' => ['host' => env('REDIS_HOST'), 'auth' => env('REDIS_AUTH'), 'port' => (int) env('REDIS_PORT', 6379), 'db' => (int) env('REDIS_DB', 0), 'pool' => ['min_connections' => 5, 'max_connections' => 30, ...]],
'cache' => ['db' => (int) env('REDIS_CACHE_DB', 1), 'pool' => [...]] // 独立连接池
];
```
## Cache-Aside 模式
CacheService`withCache($key, $ttl, $fetcher)` — get 命中则返回,否则 fetcher() 后 setexTTL 加 jitter 防 stampede。`invalidate($keys)``invalidatePattern($pattern)` 用 scan 分批 del。
## CachedRepository 装饰器
实现 RepositoryInterface包装 baseRepo。getById/getPageList 用 withCachekey 格式 `{entity}:{id}``{entity}:list:{params_hash}`。invalidateDetail/invalidateList/invalidateAll。DI 注册时用 closure 返回 new CachedRepository(baseRepo, cacheService, 'order', detailTtl, listTtl)。Service 写操作后判断 `$repository instanceof CachedRepository` 则 invalidateAll。
## TTL 规范
Profile 30m、列表 5m、配置 1h、JWT 2h、Session 24h、限流与窗口对齐、验证码 5m、锁 任务时长+30s。
## Key 命名
`<namespace>:<entity>:<id>:<field>`,如 user:profile:123、orders:list:page:1、rate:limit:ip:api、session:user:456、lock:order:789、ws:connection:fd:100。
## 分布式锁
RedisLockServicewithLock($lockKey, $ttlMs, $fn)。SET NX PX。释放用 Lua 脚本原子校验 value 后 DEL。
## 缓存穿透/击穿
穿透:不存在 key 缓存 NULL_PLACEHOLDERTTL 30s。击穿热点 key 用 withLock 互斥double-check 后 fetcher 再 setex。
## 装饰器 vs 直接缓存 决策
读写比 > 10:1 → CachedRepository。读写比 < 10:1 → Service 内 CacheService。热点数据排行榜→ Redis 原生 ZSET/INCR。

View File

@@ -0,0 +1,73 @@
---
name: refactoring
version: 1.1.0
description: "基于测试保障的安全重构工作流,行为不变前提下改善代码结构。当需要重构、清理代码或消除技术债时使用。"
requires: [vue-testing]
---
> ⚠️ 核心执行流程已在 `.cursor/rules/033-refactoring.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Refactoring Workflow
## 触发条件
用户要求重构代码、清理技术债、优化代码结构或简化复杂逻辑。
## 核心原则
**重构 = 不改变外部行为的前提下改善内部结构**
- 每一步都要小到"显然正确"
- 每一步之后测试必须全部通过
- 如果测试不足,先补测试再重构
## 执行流程
### 1. 评估现状
1. 读取目标代码,识别"代码坏味道"
2. 运行现有测试确认基线:`npm test -- --related`
3. 如果测试覆盖不足,**先补充测试**
### 2. 制定重构计划
常见重构模式:
| 坏味道 | 重构手法 | 风险 |
|--------|---------|------|
| 函数过长(>50行 | Extract Function | 低 |
| 重复代码 | Extract + Consolidate | 低 |
| 过长参数列表 | Introduce Parameter Object | 低 |
| Switch/If 过多 | Replace with Polymorphism | 中 |
| 大类 | Extract Class | 中 |
| Feature Envy | Move Method | 中 |
| 深层嵌套 | Replace Nested with Guard Clauses | 低 |
### 3. 小步执行
对每一步:
1. 做一个**原子级**的重构变更
2. 运行测试 → 全部通过
3. Git commit可选但建议频繁提交
4. 继续下一步
### 4. 验证
1. 运行完整测试套件
2. ESLint 无报错
3. 功能行为未改变
## 禁止事项
- ❌ 不要在重构中同时添加新功能
- ❌ 不要一次改太多(改完跑不了测试)
- ❌ 不要在没有测试的情况下重构核心逻辑
- ❌ 不要重构正在被其他人修改的代码
## 验证
1. [ ] 所有测试通过
2. [ ] ESLint 无报错
3. [ ] 外部行为未改变
4. [ ] 代码可读性提升(主观判断)

View File

@@ -0,0 +1,81 @@
---
name: security-audit
version: 3.0.0
description: "遵循 OWASP Top 10 对 PHP Hyperf + Vue 3 应用进行安全审查和加固。当需要安全扫描、漏洞检测或安全加固时使用。"
---
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-security-audit.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# 🔒 Security Audit (PHP Hyperf + Vue 3)
## ⚠️ 安全等级RED — 审计结果包含敏感信息
## 触发条件
用户要求安全审查、漏洞扫描、安全加固或合规性检查。
## 执行流程
### 1. 依赖安全扫描
`npm audit --audit-level=high``composer audit`
### 2. OWASP Top 10 检查
| # | 风险 | 检查方法 |
|---|------|---------|
| A01 | 访问控制失效 | auth middleware + 数据权限 |
| A02 | 加密失败 | bcrypt/Argon2id、HTTPS、JWT 强度 |
| A03 | 注入 | 参数化查询、ORM、用户输入 |
| A04 | 不安全设计 | 业务逻辑、并发、分布式锁 |
| A05 | 安全配置 | CORS、headers、debug 关闭 |
| A06 | 过时组件 | npm/composer audit |
| A07 | 认证失败 | JWT 双 Token、密码策略、登录限流 |
| A08 | 数据完整性 | 反序列化、CI/CD |
| A09 | 日志监控 | 覆盖率和告警 |
| A10 | SSRF | HTTP 请求目标验证 |
### 35. PHP / 前端 / 密钥扫描
PHPStan、危险函数eval/exec/system 等、反序列化、文件包含、SQL 拼接。前端 v-html、eval、innerHTML、localStorage Token、target=_blank 无 noopener。密钥格式AWS/GitHub/Stripe/Google/JWT/私钥/连接串。完整命令见 **Tier 3**
### 6. 安全头与 CORS
CSP、HSTS、X-Content-Type-Options、X-Frame-Options、X-XSS-Protection、Referrer-Policy。CORS 必须在中间件集中配置,生产无 Allow-Origin: *。
### 6.56.7. CodeGuard 增强
密码哈希 Argon2id、IDOR、Mass Assignment、Session Cookie、禁用算法、文件上传、SSRF。完整检查与示例见 **Tier 3**
### 7. 中间件链验证
CorsMiddleware → TraceId → RequestLog → Auth → Permission → RateLimit → RequestSign。
### 8. 输出报告
Critical/High/Medium/Low 分级。
## 验证
1. [ ] OWASP Top 10 逐项完成
2. [ ] npm/composer audit 无 critical/high
3. [ ] PHPStan 无错误
4. [ ] 无硬编码密钥
5. [ ] 安全头已配置
6. [ ] 中间件链完整
7. [ ] 无危险函数
8. [ ] 无 IDOR、Mass Assignment
9. [ ] 无禁用算法,密码 Argon2id
10. [ ] Session Cookie Secure+HttpOnly+SameSite
11. [ ] 文件上传 UUID + magic bytes
12. [ ] 外部请求有白名单
13. [ ] CORS 集中配置
## Tier 3 深度参考
| 文件 | 内容 |
|------|------|
| `references/audit-commands.md` | PHP/前端/密钥/CodeGuard 完整检查命令与最佳实践 |
| `references/codeguard/*` | CodeGuard 各维度详细策略 |
| `references/security-headers.md` | Nginx 安全响应头 |

View File

@@ -0,0 +1,56 @@
# Security Audit — 检查命令与代码示例
> 主流程见 SKILL.md本文档为 PHP/前端/密钥/CodeGuard 的完整检查命令和最佳实践。
## 依赖扫描
```bash
npm audit --audit-level=high
composer audit
```
## PHP 检查命令
```bash
vendor/bin/phpstan analyse --level=max
rg -n "eval\(|exec\(|system\(|passthru\(|shell_exec\(|popen\(" --type php --glob '!vendor/**'
rg -n "unserialize\(" --type php --glob '!vendor/**'
rg -n "include\s*\\\$|require\s*\\\$" --type php --glob '!vendor/**'
rg -n "Db::raw\(|DB::select\(.*\\\$" --type php --glob '!vendor/**'
```
## 前端检查命令
```bash
rg -n "v-html" --glob '*.vue' --glob '*.ts'
rg -n "eval\(|new Function\(" --glob '*.ts' --glob '!node_modules/**'
rg -n "\.innerHTML\s*=|\.outerHTML\s*=" --glob '*.vue' --glob '!node_modules/**'
rg -n "localStorage\.(set|get)Item.*[Tt]oken" --glob '*.ts' --glob '!node_modules/**'
rg -n "target=\"_blank\"" --glob '*.vue' | rg -v "noopener"
```
## 密钥扫描命令
```bash
# 通用、AWS、GitHub、Stripe、Google、JWT、私钥、数据库连接串
rg -rn "(?i)(api.?key|secret|password|token)\s*[=:]\s*['\"][a-zA-Z0-9]{8,}" --glob '!vendor/**' --glob '!node_modules/**'
rg -rn "A(KIA|GPA|IDA|ROA)[0-9A-Z]{16}" ...
rg -rn "gh[pousr]_[a-zA-Z0-9]{36}" ...
rg -rn "sk_live_|pk_live_|sk_test_[a-zA-Z0-9]{24}" ...
rg -rn "AIza[a-zA-Z0-9_\-]{35}" ...
rg -rn "eyJ[a-zA-Z0-9_\-]+\.[a-zA-Z0-9_\-]+\.[a-zA-Z0-9_\-]+" ...
rg -rn "BEGIN\s+(RSA\s+)?PRIVATE\s+KEY" ...
rg -rn "(mysql|mongodb|redis|postgres)://[^:]+:[^@]+" ...
```
## CORS 集中配置
❌ Controller 内单独设置 Allow-Origin: *。✅ CorsMiddleware 集中配置env('CORS_ORIGIN')Allow-Credentials 与 Allow-Origin: * 不共存。
## 密码哈希
❌ md5/sha1、PASSWORD_BCRYPT 无 cost。✅ PASSWORD_ARGON2IDmemory_cost 65536, time_cost 4或 PASSWORD_BCRYPT cost=12。验证用 password_verify升级用 password_needs_rehash。
## CodeGuard 增强
IDORfind/findOrFail($request/$id) 未附加所有权 → 应 $user->orders()->findOrFail($id)。Mass Assignmentfill($request->all())、create/update($request->all())。Sessionsetcookie 需 Secure+HttpOnly+SameSite。禁用算法md5/sha1、AES-ECB。文件上传不用 getClientFilename() 原始名,用 UUIDMIME 用 finfo magic bytes。SSRFGuzzle/curl 对用户 URL 需域名白名单。

View File

@@ -0,0 +1,82 @@
---
description: API & Web services security (REST/GraphQL/SOAP), schema validation, authn/z, SSRF
languages:
- c
- go
- java
- typescript
- php
- python
- ruby
- xml
- yaml
alwaysApply: false
---
rule_id: codeguard-0-api-web-services
## API & Web Services Security
Secure REST, GraphQL, and SOAP/WS services endtoend: transport, authn/z, schema validation, SSRF controls, DoS limits, and microservicesafe patterns.
### Transport and TLS
- HTTPS only; consider mTLS for highvalue/internal services. Validate certs (CN/SAN, revocation) and prevent mixed content.
### Authentication and Tokens
- Use standard flows (OAuth2/OIDC) for clients; avoid custom schemes. For services, use mTLS or signed service tokens.
- JWTs: pin algorithms; validate iss/aud/exp/nbf; short lifetimes; rotation; denylist on logout/revoke. Prefer opaque tokens when revocation is required and central store is available.
- API keys: scope narrowly; rate limit; monitor usage; do not use alone for sensitive operations.
### Authorization
- Enforce perendpoint, perresource checks serverside; deny by default.
- For microservices, authorize at gateway (coarse) and service (fine) layers; propagate signed internal identity, not external tokens.
### Input and Content Handling
- Validate inputs via contracts: OpenAPI/JSON Schema, GraphQL SDL, XSD. Reject unknown fields and oversize payloads; set limits.
- Content types: enforce explicit ContentType/Accept; reject unsupported combinations. Harden XML parsers against XXE/expansion.
### SQL/Injection Safety in Resolvers and Handlers
- Use parameterized queries/ORM bind parameters; never concatenate user input into queries or commands.
### GraphQLSpecific Controls
- Limit query depth and overall complexity; enforce pagination; timeouts on execution; disable introspection and IDEs in production.
- Implement field/objectlevel authorization to prevent IDOR/BOLA; validate batching and rate limit per object type.
### SSRF Prevention for Outbound Calls
- Do not accept raw URLs. Validate domains/IPs using libraries; restrict to HTTP/HTTPS only (block file://, gopher://, ftp://, etc.).
- Case 1 (fixed partners): strict allowlists; disable redirects; network egress allowlists.
- Case 2 (arbitrary): block private/linklocal/localhost ranges; resolve and verify all IPs are public; require signed tokens from the target where feasible.
### SOAP/WS and XML Safety
- Validate SOAP payloads with XSD; limit message sizes; enable XML signatures/encryption where required.
- Configure parsers against XXE, entity expansion, and recursive payloads; scan attachments.
### Rate Limiting and DoS
- Apply perIP/user/client limits, circuit breakers, and timeouts. Use serverside batching and caching to reduce load.
### Management Endpoints
- Do not expose over the Internet. Require strong auth (MFA), network restrictions, and separate ports/hosts.
### Testing and Assessment
- Maintain formal API definitions; drive contract tests and fuzzing from specs.
- Assess endpoints for authn/z bypass, SSRF, injection, and information leakage; log token validation failures.
### Microservices Practices
- Policyascode with embedded decision points; sidecar or library PDPs.
- Service identity via mTLS or signed tokens; never reuse external tokens internally.
- Centralized structured logging with correlation IDs; sanitize sensitive data.
### Implementation Checklist
- HTTPS/mTLS configured; certs managed; no mixed content.
- Contract validation at the edge and service; unknown fields rejected; size/time limits enforced.
- Strong authn/z per endpoint; GraphQL limits applied; introspection disabled in prod.
- SSRF protections at app and network layers; redirects disabled; allowlists where possible.
- Rate limiting, circuit breakers, and resilient patterns in place.
- Management endpoints isolated and strongly authenticated.
- Logs structured and privacysafe with correlation IDs.
### Test Plan
- Contract tests for schema adherence; fuzzing with schemaaware tools.
- Pen tests for SSRF, IDOR/BOLA, and authz bypass; performance tests for DoS limits.
- Test all HTTP methods per endpoint; discover parameters in URL paths, headers, and structured data beyond obvious query strings.
- Automated checks for token validation and revocation behavior.

View File

@@ -0,0 +1,103 @@
---
description: Authentication and MFA best practices (passwords, MFA, OAuth/OIDC, SAML, recovery, tokens)
languages:
- c
- go
- java
- typescript
- kotlin
- matlab
- php
- python
- ruby
- swift
alwaysApply: false
---
rule_id: codeguard-0-authentication-mfa
## Authentication & MFA
Build a resilient, user-friendly authentication system that resists credential attacks, protects secrets, and supports strong, phishing-resistant MFA and secure recovery.
### Account Identifiers and UX
- Use non-public, random, and unique internal user identifiers. Allow login via verified email or username.
- Always return generic error messages (e.g., "Invalid username or password"). Keep timing consistent to prevent account enumeration.
- Support password managers: `<input type="password">`, allow paste, no JS blocks.
### Password Policy
- Accept passphrases and full Unicode; minimum 8 characters; avoid composition rules. Set only a reasonable maximum length (64+).
- Check new passwords against breach corpora (e.g., kanonymity APIs); reject breached/common passwords.
### Password Storage (Hashing)
- Hash, do not encrypt. Use slow, memoryhard algorithms with unique peruser salts and constanttime comparison.
- Preferred order and parameters (tune to your hardware; target <1s on server):
- Argon2id: m=1946 MiB, t=21, p=1 (or equivalent security tradeoffs)
- scrypt: N=2^17, r=8, p=1 (or equivalent)
- bcrypt (legacy only): cost ≥10, be aware of 72byte input limit
- PBKDF2 (FIPS): PBKDF2HMACSHA256 ≥600k, or SHA1 ≥1.3M
- Optional pepper: store outside DB (KMS/HSM); if used, apply via HMAC or prehashing. Plan for user resets if pepper rotates.
- Unicode and null bytes must be supported endtoend by the library.
### Authentication Flow Hardening
- Enforce TLS for all auth endpoints and token transport; enable HSTS.
- Implement rate limits per IP, account, and globally; add proofofwork or CAPTCHA only as last resort.
- Lockouts/throttling: progressive backoff; avoid permanent lockout via resets/alerts.
- Uniform responses and code paths to reduce oracle/timing signals.
### MultiFactor Authentication (MFA)
- Adopt phishingresistant factors by default for sensitive accounts: passkeys/WebAuthn (FIDO2) or hardware U2F.
- Acceptable: TOTP (appbased), smart cards with PIN. Avoid for sensitive use: SMS/voice, email codes; never rely on security questions.
- Require MFA for: login, password/email changes, disabling MFA, privilege elevation, highvalue transactions, new devices/locations.
- Riskbased MFA signals: new device, geovelocity, IP reputation, unusual time, breached credentials.
- MFA recovery: provide singleuse backup codes, encourage multiple factors, and require strong identity verification for resets.
- Handle failed MFA: offer alternative enrolled methods, notify users of failures, and log context (no secrets).
### Federation and Protocols (OAuth 2.0 / OIDC / SAML)
- Use standard protocols only; do not build your own.
- OAuth 2.0/OIDC:
- Prefer Authorization Code with PKCE for public/native apps; avoid Implicit and ROPC.
- Validate state and nonce; use exact redirect URI matching; prevent open redirects.
- Constrain tokens to audience/scope; use DPoP or mTLS for senderconstraining when possible.
- Rotate refresh tokens; revoke on logout or risk signals.
- SAML:
- TLS 1.2+; sign responses/assertions; encrypt sensitive assertions.
- Validate issuers, InResponseTo, timestamps (NotBefore/NotOnOrAfter), Recipient; verify against trusted keys.
- Prevent XML signature wrapping with strict schema validation and hardened XPath selection.
- Keep response lifetimes short; prefer SPinitiated flows; validate RelayState; implement replay detection.
### Tokens (JWT and Opaque)
- Prefer opaque servermanaged tokens for simplicity and revocation. If using JWTs:
- Explicitly pin algorithms; reject "none"; validate iss/aud/exp/iat/nbf; use short lifetimes and rotation.
- Store secrets/keys securely (KMS/HSM). Use strong HMAC secrets or asymmetric keys; never hardcode.
- Consider binding tokens to a client context (e.g., fingerprint hash in cookie) to reduce replay.
- Implement denylist/allowlist for revocation on logout and critical events.
### Recovery and Reset
- Return the same response for existing and nonexisting accounts (no enumeration). Normalize timing.
- Generate 32+ byte, CSPRNG tokens; singleuse; store as hashes; short expiry.
- Use HTTPS reset links to pinned, trusted domains; add referrer policy (noreferrer) on UI.
- After reset: require reauthentication, rotate sessions, and do not autologin.
- Never lock accounts due to reset attempts; ratelimit and monitor instead.
### Administrative and Internal Accounts
- Separate admin login from public forms; enforce stronger MFA, device posture checks, IP allowlists, and stepup auth.
- Use distinct session contexts and stricter timeouts for admin operations.
### Monitoring and Signals
- Log auth events (failures/successes, MFA enroll/verify, resets, lockouts) with stable fields and correlation IDs; never log secrets or raw tokens.
- Detect credential stuffing: high failure rates, many IPs/agents, impossible travel. Notify users of new device logins.
### Implementation Checklist
- Passwords: Argon2id (preferred) with peruser salt, constanttime verify; breached password checks on change/set.
- MFA: WebAuthn/passkeys or hardware tokens for highrisk; TOTP as fallback; secure recovery with backup codes.
- Federation: Authorization Code + PKCE; strict redirect URI validation; audience/scope enforced; token rotation.
- Tokens: shortlived, senderconstrained where possible; revocation implemented; secrets in KMS/HSM.
- Recovery: singleuse, hashed, timeboxed tokens; consistent responses; reauth required after reset; sessions rotated.
- Abuse: rate limits, throttling, and anomaly detection on auth endpoints; uniform error handling.
- Admin: isolated flows with stricter policies and device checks.
### Test Plan
- Unit/integration tests for login, MFA enroll/verify, resets, and lockouts with uniform errors.
- Protocol tests: PKCE, state/nonce, redirect URI validation, token audience/scope.
- Dynamic tests for credential stuffing resistance and token replay; validate revocation after logout and role change.

View File

@@ -0,0 +1,59 @@
---
description: Authorization and access control (RBAC/ABAC/ReBAC, IDOR, mass assignment, transaction auth)
languages:
- c
- go
- java
- typescript
- php
- python
- ruby
- yaml
alwaysApply: false
---
rule_id: codeguard-0-authorization-access-control
## Authorization & Access Control
Enforce least privilege and precise access decisions for every request and resource, prevent IDOR and mass assignment, and provide strong transaction authorization where necessary.
### Core Principles
1. Deny by Default: The default for any access request should be 'deny'. Explicitly grant permissions to roles or users rather than explicitly denying them. When no allow rule matches, return HTTP 403 Forbidden.
2. Principle of Least Privilege: Grant users the minimum level of access required to perform their job functions. Regularly audit permissions to ensure they are not excessive.
3. Validate Permissions on Every Request: Check authorization for every single request, regardless of source (AJAX, API, direct). Use middleware/filters to ensure consistent enforcement.
4. Prefer ABAC/ReBAC over RBAC: Use Attribute-Based Access Control (ABAC) or Relationship-Based Access Control (ReBAC) for fine-grained permissions instead of simple role-based access control.
### Systemic Controls
- Centralize authorization at service boundaries via middleware/policies/filters.
- Model permissions at the resource level (ownership/tenancy) and enforce scoping in data queries.
- Return generic 403/404 responses to avoid leaking resource existence.
- Log all denials with user, action, resource identifier (non-PII), and rationale code.
### Preventing IDOR
- Never trust user-supplied identifiers alone. Always verify access to each object instance.
- Resolve resources through user-scoped queries or server-side lookups. Example: `currentUser.projects.find(id)` instead of `Project.find(id)`.
- Use non-enumerable identifiers (UUIDs/random) as defense-in-depth. Do not rely on obscurity alone.
### Preventing Mass Assignment
- Do not bind request bodies directly to domain objects containing sensitive fields.
- Expose only safe, editable fields via DTOs. Maintain explicit allow-lists for patch/update.
- Use framework features to block-list sensitive fields if allow-listing is infeasible.
### Transaction Authorization (Step-Up)
- Require a second factor for sensitive actions (wire transfers, privilege elevation, data export). Apply WhatYouSeeIsWhatYouSign: show critical fields for user confirmation.
- Use unique, timelimited authorization credentials per transaction; reject on data changes midflow.
- Enforce the chosen authorization method server-side; prevent clientside downgrades.
- Protect against brute-force with throttling and complete flow restarts after failures.
### Testing and Automation
- Maintain an authorization matrix (YAML/JSON) listing endpoints/resources, roles/attributes, and expected outcomes.
- Automate integration tests that iterate the matrix, mint role tokens, and assert allow/deny results—including token expiry/revocation cases.
- Exercise negative tests: swapped IDs, downgraded roles, missing scopes, and bypass attempts.
### Implementation Checklist
- Middleware/policies enforce deny-by-default and resource checks on every endpoint.
- Query scoping ensures users only access permitted rows/objects.
- DTOs and allow-lists prevent mass assignment; sensitive fields never bindable.
- Step-up authorization in place for sensitive operations with unique, short-lived credentials.
- Authorization matrix drives CI tests; failures block merges.

View File

@@ -0,0 +1,111 @@
---
description: Client-side web security (XSS/DOM XSS, CSP, CSRF, clickjacking, XS-Leaks, third-party JS)
languages:
- c
- html
- typescript
- php
- vlang
alwaysApply: false
---
rule_id: codeguard-0-client-side-web-security
## Clientside Web Security
Protect browser clients against code injection, request forgery, UI redress, crosssite leaks, and unsafe thirdparty scripts with layered, contextaware controls.
### XSS Prevention (ContextAware)
- HTML context: prefer `textContent`. If HTML is required, sanitize with a vetted library (e.g., DOMPurify) and strict allowlists.
- Attribute context: always quote attributes and encode values.
- TypeScript context: do not build JS from untrusted strings; avoid inline event handlers; use `addEventListener`.
- URL context: validate protocol/domain and encode; block `typescript:` and data URLs where inappropriate.
- Redirects/forwards: never use user input directly for destinations; use server-side mapping (ID→URL) or validate against trusted domain allowlists.
- CSS context: allowlist values; never inject raw style text from users.
Example sanitization:
```typescript
const clean = DOMPurify.sanitize(userHtml, {
ALLOWED_TAGS: ['b','i','p','a','ul','li'],
ALLOWED_ATTR: ['href','target','rel'],
ALLOW_DATA_ATTR: false
});
```
### DOMbased XSS and Dangerous Sinks
- Prohibit `innerHTML`, `outerHTML`, `document.write` with untrusted data.
- Prohibit `eval`, `new Function`, stringbased `setTimeout/Interval`.
- Validate and encode data before assigning to `location` or event handler properties.
- Use strict mode and explicit variable declarations to prevent global namespace pollution from DOM clobbering.
- Adopt Trusted Types and enforce strict CSP to prevent DOM sinks exploitation.
Trusted Types + CSP:
```http
Content-Security-Policy: script-src 'self' 'nonce-{random}'; object-src 'none'; base-uri 'self'; require-trusted-types-for 'script'
```
### Content Security Policy (CSP)
- Prefer noncebased or hashbased CSP over domain allowlists.
- Start with ReportOnly mode; collect violations; then enforce.
- Baseline to aim for: `default-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; form-action 'self'; object-src 'none'; base-uri 'none'; upgrade-insecure-requests`.
### CSRF Defense
- Fix XSS first; then layer CSRF defenses.
- Use frameworknative CSRF protections and synchronizer tokens on all statechanging requests.
- Cookie settings: `SameSite=Lax` or `Strict`; sessions `Secure` and `HttpOnly`; use `__Host-` prefix when possible.
- Validate Origin/Referer; require custom headers for API mutations in SPA token models.
- Never use GET for state changes; validate tokens on POST/PUT/DELETE/PATCH only. Enforce HTTPS for all token transmission.
### Clickjacking Defense
- Primary: `Content-Security-Policy: frame-ancestors 'none'` or a specific allowlist.
- Fallback for legacy browsers: `X-Frame-Options: DENY` or `SAMEORIGIN`.
- Consider UX confirmations for sensitive actions when framing is required.
### CrossSite Leaks (XSLeaks) Controls
- Use `SameSite` cookies appropriately; prefer `Strict` for sensitive actions.
- Adopt Fetch Metadata protections to block suspicious crosssite requests.
- Isolate browsing contexts: COOP/COEP and CORP where applicable.
- Disable caching and add userunique tokens for sensitive responses to prevent cache probing.
### ThirdParty TypeScript
- Minimize and isolate: prefer sandboxed iframes with `sandbox` and postMessage origin checks.
- Use Subresource Integrity (SRI) for external scripts and monitor for changes.
- Provide a firstparty, sanitized data layer; deny direct DOM access from tags where possible.
- Govern via tag manager controls and vendor contracts; keep libraries updated.
SRI example:
```html
<script src="https://cdn.vendor.com/app.ts"
integrity="sha384-..." crossorigin="anonymous"></script>
```
### HTML5, CORS, WebSockets, Storage
- postMessage: always specify exact target origin; verify `event.origin` on receive.
- CORS: avoid `*`; allowlist origins; validate preflights; do not rely on CORS for authz.
- WebSockets: require `wss://`, origin checks, auth, message size limits, and safe JSON parsing.
- Client storage: never store secrets in `localStorage`/`sessionStorage`; prefer HttpOnly cookies; if unavoidable, isolate via Web Workers.
- Links: add `rel="noopener noreferrer"` to external `target=_blank` links.
### HTTP Security Headers (Client Impact)
- HSTS: enforce HTTPS everywhere.
- XContentTypeOptions: `nosniff`.
- ReferrerPolicy and PermissionsPolicy: restrict sensitive signals and capabilities.
### AJAX and Safe DOM APIs
- Avoid dynamic code execution; use function callbacks, not strings.
- Build JSON with `JSON.stringify`; never via string concatenation.
- Prefer creating elements and setting `textContent`/safe attributes over raw HTML insertion.
### Implementation Checklist
- Contextual encoding/sanitization for every sink; no dangerous APIs without guards.
- Strict CSP with nonces and Trusted Types; violations monitored.
- CSRF tokens on all statechanging requests; secure cookie attributes.
- Frame protections set; XSLeak mitigations enabled (Fetch Metadata, COOP/COEP/CORP).
- Thirdparty JS isolated with SRI and sandbox; vetted data layer only.
- HTML5/CORS/WebSocket usage hardened; no secrets in web storage.
- Security headers enabled and validated.
### Test Plan
- Automated checks for dangerous DOM/API patterns.
- E2E tests for CSRF and clickjacking; CSP report monitoring.
- Manual probes for XSLeaks (frame count, timing, cache) and open redirect behavior.

View File

@@ -0,0 +1,74 @@
---
description: Secure file handling & uploads (validation, storage isolation, scanning, safe delivery)
languages:
- c
- go
- java
- typescript
- php
- python
- ruby
alwaysApply: false
---
rule_id: codeguard-0-file-handling-and-uploads
## File Upload Security Guidelines
This rule advises on secure file upload practices to prevent malicious file attacks and protect system integrity:
- Extension Validation
- List allowed extensions only for business-critical functionality.
- Ensure input validation is applied before validating extensions.
- Avoid double extensions (e.g., `.jpg.php`) and null byte injection (e.g., `.php%00.jpg`).
- Use allowlist approach rather than denylist for file extensions.
- Validate extensions after decoding filename to prevent bypass attempts.
- Content Type and File Signature Validation
- Never trust client-supplied Content-Type headers as they can be spoofed.
- Validate file signatures (magic numbers) in conjunction with Content-Type checking.
- Implement allowlist approach for MIME types as a quick protection layer.
- Use file signature validation but not as a standalone security measure.
- Filename Security
- Generate random filenames (UUID/GUID) instead of using user-supplied names.
- If user filenames required, implement maximum length limits.
- Restrict characters to alphanumeric, hyphens, spaces, and periods only.
- Prevent leading periods (hidden files) and sequential periods (directory traversal).
- Avoid leading hyphens or spaces for safer shell script processing.
- File Content Validation
- For images, apply image rewriting techniques to destroy malicious content.
- For Microsoft documents, use Apache POI for validation.
- Avoid ZIP files due to numerous attack vectors.
- Implement manual file review in sandboxed environments when resources allow.
- Integrate antivirus scanning and Content Disarm & Reconstruct (CDR) for applicable file types.
- Storage Security
- Store files on different servers for complete segregation when possible.
- Store files outside webroot with administrative access only.
- If storing in webroot, set write-only permissions with proper access controls.
- Use application handlers that map IDs to filenames for public access.
- Consider database storage for specific use cases with DBA expertise.
- Access Control and Authentication
- Require user authentication before allowing file uploads.
- Implement proper authorization levels for file access and modification.
- Set filesystem permissions on principle of least privilege.
- Scan files before execution if execution permission is required.
- Upload and Download Limits
- Set proper file size limits for upload protection.
- Consider post-decompression size limits for compressed files.
- Implement request limits for download services to prevent DoS attacks.
- Use secure methods to calculate ZIP file sizes safely.
- Additional Security Measures
- Protect file upload endpoints from CSRF attacks.
- Keep all file processing libraries securely configured and updated.
- Implement logging and monitoring for upload activities.
- Provide user reporting mechanisms for illegal content.
- Use secure extraction methods for compressed files.
Summary:
Implement defense-in-depth for file uploads through multi-layered validation, secure storage practices, proper access controls, and comprehensive monitoring. Never rely on single validation methods and always generate safe filenames to prevent attacks.

View File

@@ -0,0 +1,107 @@
---
description: Input validation and injection defense (SQL/SOQL/LDAP/OS), parameterization, prototype pollution
languages:
- apex
- c
- go
- html
- java
- typescript
- php
- powershell
- python
- ruby
- shell
- sql
alwaysApply: false
---
rule_id: codeguard-0-input-validation-injection
## Input Validation & Injection Defense
Ensure untrusted input is validated and never interpreted as code. Prevent injection across SQL, LDAP, OS commands, templating, and TypeScript runtime object graphs.
### Core Strategy
- Validate early at trust boundaries with positive (allowlist) validation and canonicalization.
- Treat all untrusted input as data, never as code. Use safe APIs that separate code from data.
- Parameterize queries/commands; escape only as last resort and contextspecific.
### Validation Playbook
- Syntactic validation: enforce format, type, ranges, and lengths for each field.
- Semantic validation: enforce business rules (e.g., start ≤ end date, enum allowlists).
- Normalization: canonicalize encodings before validation; validate complete strings (regex anchors ^$); beware ReDoS.
- Freeform text: define character class allowlists; normalize Unicode; set length bounds.
- Files: validate by content type (magic), size caps, and safe extensions; servergenerate filenames; scan; store outside web root.
### SQL Injection Prevention
- Use prepared statements and parameterized queries for 100% of data access.
- Use bind variables for any dynamic SQL construction within stored procedures and never concatenate user input into SQL.
- Prefer leastprivilege DB users and views; never grant admin to app accounts.
- Escaping is fragile and discouraged; parameterization is the primary defense.
Example (Java PreparedStatement):
```java
String custname = request.getParameter("customerName");
String query = "SELECT account_balance FROM user_data WHERE user_name = ? ";
PreparedStatement pstmt = connection.prepareStatement( query );
pstmt.setString( 1, custname);
ResultSet results = pstmt.executeQuery( );
```
### SOQL/SOSL Injection (Salesforce)
SOQL and SOSL are query/search languages (no SQL-style DDL/DML). Data changes are performed via Apex DML or Database methods. Note: SOQL can lock rows via `FOR UPDATE`.
- Primary risk: data exfiltration by bypassing intended query filters/business logic; impact is amplified when Apex runs with elevated access (system mode) or when CRUD/FLS aren't enforced.
- Second-order risk (conditional): if queried records are passed to DML, injection can broaden the record set and cause unintended mass updates/deletes.
- Prefer static SOQL/SOSL with bind variables: `[SELECT Id FROM Account WHERE Name = :userInput]` or `FIND :term`.
- For dynamic SOQL, use `Database.queryWithBinds()`; for dynamic SOSL, use `Search.query()`. Allowlist any dynamic identifiers. If concatenation is unavoidable, escape string values with `String.escapeSingleQuotes()`.
- Enforce CRUD/FLS with `WITH USER_MODE` or `WITH SECURITY_ENFORCED` (don't combine both). Enforce record sharing with `with sharing` or user-mode operations. Use `Security.stripInaccessible()` before DML.
### LDAP Injection Prevention
- Always apply contextappropriate escaping:
- DN escaping for `\ # + < > , ; " =` and leading/trailing spaces
- Filter escaping for `* ( ) \ NUL`
- Validate inputs with allowlists before constructing queries; use libraries that provide DN/filter encoders.
- Use leastprivilege LDAP connections with bind authentication; avoid anonymous binds for application queries.
### OS Command Injection Defense
- Prefer builtin APIs instead of shelling out (e.g., library calls over `exec`).
- If unavoidable, use structured execution that separates command and arguments (e.g., ProcessBuilder). Do not invoke shells.
- Strictly allowlist commands and validate arguments with allowlist regex; exclude metacharacters (& | ; $ > < ` \ ! ' " ( ) and whitespace as needed).
- Use `--` to delimit arguments where supported to prevent option injection.
Example (Java ProcessBuilder):
```java
ProcessBuilder pb = new ProcessBuilder("TrustedCmd", "Arg1", "Arg2");
Map<String,String> env = pb.environment();
pb.directory(new File("TrustedDir"));
Process p = pb.start();
```
### Query Parameterization Guidance
- Use the platforms parameterization features (JDBC PreparedStatement, .NET SqlCommand, Ruby ActiveRecord bind params, PHP PDO, SQLx bind, etc.).
- For stored procedures, ensure parameters are bound; never build dynamic SQL via string concatenation inside procedures.
### Prototype Pollution (TypeScript)
- Developers should use `new Set()` or `new Map()` instead of using object literals
- When objects are required, create with `Object.create(null)` or `{ __proto__: null }` to avoid inherited prototypes.
- Freeze or seal objects that should be immutable; consider Node `--disable-proto=delete` as defenseindepth.
- Avoid unsafe deep merge utilities; validate keys against allowlists and block `__proto__`, `constructor`, `prototype`.
### Caching and Transport
- Apply `Cache-Control: no-store` on responses containing sensitive data; enforce HTTPS across data flows.
### Implementation Checklist
- Central validators: types, ranges, lengths, enums; canonicalization before checks.
- 100% parameterization coverage for SQL; dynamic identifiers via allowlists only.
- LDAP DN/filter escaping in use; inputs validated prior to query.
- No shell invocation for untrusted input; if unavoidable, structured exec + allowlist + regex validation.
- JS object graph hardened: safe constructors, blocked prototype paths, safe merge utilities.
- File uploads validated by content, size, and extension; stored outside web root and scanned.
### Test Plan
- Static checks for string concatenation in queries/commands and dangerous DOM/merge sinks.
- Fuzzing for SQL/LDAP/OS injection vectors; unit tests for validator edge cases.
- Negative tests exercising blocked prototype keys and deep merge behavior.

View File

@@ -0,0 +1,45 @@
---
description: Logging & monitoring (structured telemetry, redaction, integrity, detection & alerting)
languages:
- c
- typescript
- yaml
alwaysApply: false
---
rule_id: codeguard-0-logging
## Logging & Monitoring
Produce structured, privacyaware telemetry that supports detection, response, and forensics without exposing secrets.
### What to Log
- Authn/authz events; admin actions; config changes; sensitive data access; input validation failures; security errors.
- Include correlation/request IDs, user/session IDs (nonPII), source IP, user agent, timestamps (UTC, RFC3339).
### How to Log
- Structured logs (JSON) with stable field names; avoid freeform text for critical signals.
- Sanitize all log inputs to prevent log injection (strip CR/LF/delimiters); validate data from other trust zones.
- Redact/tokenize secrets and sensitive fields; never log credentials, tokens, recovery codes, or raw session IDs.
- Ensure integrity: appendonly or WORM storage; tamper detection; centralized aggregation; access controls and retention policies.
### Detection & Alerting
- Build alerts for auth anomalies (credential stuffing patterns, impossible travel), privilege changes, excessive failures, SSRF indicators, and data exfil patterns.
- Tune thresholds; provide runbooks; ensure oncall coverage; test alert flows.
### Storage & Protection
- Isolate log storage (separate partition/database); strict file/directory permissions; store outside webaccessible locations.
- Synchronize time across systems; use secure protocols for transmission; implement tamper detection and monitoring.
### Privacy & Compliance
- Maintain data inventory and classification; minimize personal data in logs; honor retention and deletion policies.
- Provide mechanisms to trace and delete userlinked log data where required by policy.
### Implementation Checklist
- JSON logging enabled; log injection sanitization active; redaction filters active; correlation IDs on all requests.
- Isolated log storage with tamper detection; centralized log pipeline with integrity protections; retention configured.
- Security alerts defined and tested; dashboards and reports in place.
### Validation
- Unit/integration tests assert presence/absence of key fields; redaction unit tests.
- Periodic audits for secret/PII leakage; tabletop exercises for incident workflows.

View File

@@ -0,0 +1,78 @@
---
description: Session management and secure cookies (rotation, fixation, timeouts, theft detection)
languages:
- c
- go
- html
- java
- typescript
- php
- python
- ruby
alwaysApply: false
---
rule_id: codeguard-0-session-management-and-cookies
## Session Management & Cookies
Implement robust, attack-resistant session handling that prevents fixation, hijacking, and theft while maintaining usability.
### Session ID Generation and Properties
- Generate session IDs with a CSPRNG; ≥64 bits of entropy (prefer 128+). Opaque, unguessable, and free of meaning.
- Use generic cookie names (e.g., `id`) rather than framework defaults. Reject any incoming ID not created by the server.
- Store all session data server-side; never embed PII or privileges in the token. If sensitive, encrypt server-side session store at rest.
### Cookie Security Configuration
- Set `Secure`, `HttpOnly`, `SameSite=Strict` (or `Lax` if necessary for flows) on session cookies.
- Scope cookies narrowly with `Path` and `Domain`. Avoid cross-subdomain exposure.
- Prefer non-persistent session cookies (no Expires/Max-Age). Require full HTTPS; enable HSTS site-wide.
Example header:
```
Set-Cookie: id=<opaque>; Secure; HttpOnly; SameSite=Strict; Path=/
```
### Session Lifecycle and Rotation
- Create sessions only server-side; treat provided IDs as untrusted input.
- Regenerate session ID on authentication, password changes, and any privilege elevation. Invalidate the prior ID.
- Use distinct preauth and postauth cookie names if framework patterns require it.
### Expiration and Logout
- Idle timeout: 25 minutes for high-value, 1530 minutes for lower risk. Absolute timeout: 48 hours.
- Enforce timeouts server-side. Provide a visible logout button that fully invalidates the server session and clears the cookie client-side.
### Transport and Caching
- Enforce HTTPS for the entire session journey. Never mix HTTP/HTTPS in one session.
- Send `Cache-Control: no-store` on responses containing session identifiers or sensitive data.
### Cookie Theft Detection and Response
- Fingerprint session context server-side at establishment (IP, User-Agent, Accept-Language, relevant `sec-ch-ua` where available).
- Compare incoming requests to the stored fingerprint, allowing for benign drift (e.g., subnet changes, UA updates).
- Risk-based responses:
- High risk: require re-authentication; rotate session ID.
- Medium risk: step-up verification (challenge); rotate session ID.
- Low risk: log suspicious activity.
- Always regenerate the session ID when potential hijacking is detected.
### Client-Side Storage
- Do not store session tokens in `localStorage`/`sessionStorage` due to XSS risk. Prefer HttpOnly cookies for transport.
- If client-side storage is unavoidable for non-session secrets, isolate via Web Workers and never expose in page context.
### Framework and Multi-Cookie Scenarios
- Prefer built-in session frameworks; keep them updated and hardened.
- Validate relationships when multiple cookies participate in session state; avoid same cookie names across paths/domains.
### Monitoring and Telemetry
- Log session lifecycle events (creation, rotation, termination) using salted hashes of the session ID, not raw values.
- Monitor for brute force of session IDs and anomalous concurrent usage.
### Implementation Checklist
1) CSPRNG session IDs (≥64 bits entropy), opaque and server-issued only.
2) Cookie flags: `Secure`, `HttpOnly`, `SameSite` set; tight domain/path.
3) HTTPS-only with HSTS; no mixed content.
4) Regenerate IDs on auth and privilege changes; invalidate old IDs.
5) Idle and absolute timeouts enforced server-side; full logout implemented.
6) `Cache-Control: no-store` for sensitive responses.
7) Server-side fingerprinting and risk-based responses to anomalies.
8) No client storage of session tokens; framework defaults hardened.

View File

@@ -0,0 +1,137 @@
---
description: Cryptographic Security Guidelines & Post-Quantum Readiness
alwaysApply: true
---
rule_id: codeguard-1-crypto-algorithms
# Cryptographic Security Guidelines & Post-Quantum Readiness
## 1. Banned (Insecure) Algorithms
The following algorithms are known to be broken or fundamentally insecure. NEVER generate or use code with these algorithms.
* Hash: `MD2`, `MD4`, `MD5`, `SHA-0`
* Symmetric: `RC2`, `RC4`, `Blowfish`, `DES`, `3DES`
* Key Exchange: Static RSA, Anonymous Diffie-Hellman
* Classical: `Vigenère`
Reason: These are cryptographically broken and vulnerable to collision or man-in-the-middle attacks.
## 2. Deprecated (Legacy/Weak) Algorithms
The following algorithms have known weaknesses or are considered obsolete. Avoid in new designs and prioritize migration.
* Hash: `SHA-1`
* Symmetric: `AES-CBC`, `AES-ECB`
* Signature: RSA with `PKCS#1 v1.5` padding
* Key Exchange: DHE with weak/common primes
## 3. Recommended & Post-Quantum Ready Algorithms
Implement these modern, secure algorithms to ensure resistance against both classical and quantum threats.
### Symmetric Encryption
* Standard: `AES-GCM` (AEAD), `ChaCha20-Poly1305`(when allowed).
* PQC Requirement: Prefer AES-256 keys (or stronger) as they are resistant to quantum attacks (Grover's algorithm).
* Avoid: Custom crypto or unauthenticated modes.
### Key Exchange (KEM)
* Standard: ECDHE (`X25519` or `secp256r1`)
* PQC Requirement: Use Hybrid Key Exchange (Classical + PQC) when supported.
* Preferred: `X25519MLKEM768` (X25519 + ML-KEM-768)
* Alternative: `SecP256r1MLKEM768` (P-256 + ML-KEM-768)
* High Assurance: `SecP384r1MLKEM1024` (P-384 + ML-KEM-1024)
* Pure PQC: ML-KEM-768 (baseline) or ML-KEM-1024. Avoid ML-KEM-512 unless explicitly risk-accepted.
* Constraints:
* Use vendor-documented identifiers (RFC 9242/9370).
* Remove legacy/draft "Hybrid-Kyber" groups (e.g., `X25519Kyber`) and draft or hardcoded OIDs.
### Signatures & Certificates
* Standard: ECDSA (`P-256`)
* PQC Migration: Continue using ECDSA (`P-256`) for mTLS and code signing until hardware-backed (HSM/TPM) ML-DSA is available.
* Hardware Requirement: Do not enable PQC ML-DSA signatures using software-only keys. Require HSM/TPM storage.
### Protocol Versions
* (D)TLS: Enforce (D)TLS 1.3 only (or later).
* IPsec: Enforce IKEv2 only.
* Use ESP with AEAD (AES-256-GCM).
* Require PFS via ECDHE.
* Implement RFC 9242 and RFC 9370 for Hybrid PQC (ML-KEM + ECDHE).
* Ensure re-keys (CREATE_CHILD_SA) maintain hybrid algorithms.
* SSH: Enable only vendor-supported PQC/hybrid KEX (e.g., `sntrup761x25519`).
## 4. Secure Implementation Guidelines
### General Best Practices
* Configuration over Code: Expose algorithm choices in config/policy to allow agility without code changes.
* Key Management:
* Use KMS/HSM for key storage.
* Generate keys with a CSPRNG.
* Separate encryption keys from signature keys.
* Rotate keys per policy.
* NEVER hardcode keys, secrets, or experimental OIDs.
* Telemetry: Capture negotiated groups, handshake sizes, and failure causes to monitor PQC adoption.
### Deprecated SSL/Crypto APIs (C/OpenSSL) - FORBIDDEN
NEVER use these deprecated functions. Use the replacement EVP high-level APIs.
#### Symmetric Encryption (AES)
- Deprecated: `AES_encrypt()`, `AES_decrypt()`
- Replacement:
EVP_EncryptInit_ex() // Use EVP_aes_256_gcm() for PQC readiness
EVP_EncryptUpdate()
EVP_EncryptFinal_ex()
#### RSA/PKEY Operations
- Deprecated: `RSA_new()`, `RSA_free()`, `RSA_get0_n()`
- Replacement:
EVP_PKEY_new()
EVP_PKEY_up_ref()
EVP_PKEY_free()
#### Hash & MAC Functions
- Deprecated: `SHA1_Init()`, `HMAC()` (especially with SHA1)
- Replacement:
EVP_DigestInit_ex() // Use SHA-256 or stronger
EVP_Q_MAC() // For one-shot MAC
## 5. Broccoli Project Specific Requirements
- HMAC() with SHA1: Deprecated.
- Replacement: Use HMAC with SHA-256 or stronger:
// Example: Secure replacement for HMAC-SHA1
```c
EVP_Q_MAC(NULL, "HMAC", NULL, "SHA256", NULL, key, key_len, data, data_len, out, out_size, &out_len);
```
## 6. Secure Crypto Implementation Pattern
// Example: Secure AES-256-GCM encryption (PQC-Ready Symmetric Strength)
```c
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) handle_error();
// Use AES-256-GCM
if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv) != 1)
handle_error();
int len, ciphertext_len;
if (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len) != 1)
handle_error();
ciphertext_len = len;
if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1)
handle_error();
ciphertext_len += len;
EVP_CIPHER_CTX_free(ctx);
```

View File

@@ -0,0 +1,43 @@
---
description: No Hardcoded Credentials
alwaysApply: true
---
rule_id: codeguard-1-hardcoded-credentials
# No Hardcoded Credentials
NEVER store secrets, passwords, API keys, tokens or any other credentials directly in source code.
Treat your codebase as public and untrusted. Any credential that appears in source code is compromised and must be handled through secure alternatives.
#### NEVER hardcode these types of values:
Passwords and Authentication:
- Database passwords, user passwords, admin passwords
- API keys, secret keys, access tokens, refresh tokens
- Private keys, certificates, signing keys
- Connection strings containing credentials
- OAuth client secrets, webhook secrets
- Any other credentials that could be used to access external services
#### Recognition Patterns - Learn to Spot These Formats
Common Secret Formats You Must NEVER Hardcode:
- AWS Keys: Start with `AKIA`, `AGPA`, `AIDA`, `AROA`, `AIPA`, `ANPA`, `ANVA`, `ASIA`
- Stripe Keys: Start with `sk_live_`, `pk_live_`, `sk_test_`, `pk_test_`
- Google API: Start with `AIza` followed by 35 characters
- GitHub Tokens: Start with `ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_`
- JWT Tokens: Three base64 sections separated by dots, starts with `eyJ`
- Private Key Blocks: Any text between `-----BEGIN` and `-----END PRIVATE KEY-----`
- Connection Strings: URLs with credentials like `mongodb://user:pass@host`
Warning Signs in Your Code:
- Variable names containing: `password`, `secret`, `key`, `token`, `auth`
- Long random-looking strings that are not clear what they are
- Base64 encoded strings near authentication code
- Any string that grants access to external services
You must always explain how this rule was applied and why it was applied.

View File

@@ -0,0 +1,42 @@
# 安全响应头配置
## Nginx 配置(推荐)
```nginx
# /etc/nginx/conf.d/security-headers.conf
# 在 server 块或 http 块中添加
add_header X-DNS-Prefetch-Control "on" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;
```
## Hyperf 中间件方式(备选)
```php
// app/Middleware/SecurityHeadersMiddleware.php
class SecurityHeadersMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request);
return $response
->withHeader('X-Content-Type-Options', 'nosniff')
->withHeader('X-Frame-Options', 'DENY')
->withHeader('X-XSS-Protection', '1; mode=block')
->withHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
}
}
```
## 验证安全头
```bash
curl -I https://your-domain.com | grep -iE "(strict|content-security|x-frame|x-content|referrer)"
```

View File

@@ -0,0 +1,95 @@
---
name: skill-creator
version: 1.0.0
description: "为项目创建新的 Agent 技能。当需要将可复用流程固化为技能或创建新 SKILL.md 时使用。"
---
# Skill Creator
## 触发条件
用户要求创建、添加、修改技能skill或要求 Agent 学习新的工作流程。
## 执行流程
### 1. 需求收集
向用户确认以下信息(缺什么问什么):
| 字段 | 必填 | 说明 |
|------|------|------|
| name | ✅ | kebab-case匹配 `^[a-z0-9]+(-[a-z0-9]+)*$` |
| description | ✅ | ≤1024 字符,说明做什么 + 什么时候用 |
| 触发场景 | ✅ | 用户会用什么自然语言触发这个技能? |
| 执行步骤 | ✅ | 技能执行的具体步骤 |
| 需要 references | ❌ | 是否有深度文档需要附带 |
| 需要 scripts | ❌ | 是否有可执行脚本 |
### 2. 生成目录结构
```
.cursor/skills/<skill-name>/
├── SKILL.md # 必须
├── references/ # 可选:深度文档
│ └── *.md
├── scripts/ # 可选:自动化脚本
│ └── *.sh / *.ts
└── assets/ # 可选:模板文件
└── *.template
```
### 3. 编写 SKILL.md
使用以下模板:
```markdown
---
name: <kebab-case-name>
version: 1.0.0
description: "<一句话说明做什么>。Use when <触发场景的英文描述>。 <补充说明触发关键词:中英文都覆盖>。"
---
# <技能标题>
## 触发条件
<什么场景下使用此技能。>
## 执行流程
### 1. <步骤标题>
<具体、可执行的指令>
### 2. <步骤标题>
<具体、可执行的指令>
## 模板(如有)
<代码模板>
## 验证
完成后验证:
1. [ ] <检查项 1>
2. [ ] <检查项 2>
```
### 4. 质量检查
- [ ] `name` 符合 `^[a-z0-9]+(-[a-z0-9]+)*$`
- [ ] `description` ≤ 1024 字符
- [ ] description 包含中英文触发关键词
- [ ] 步骤用编号,每步可独立执行
- [ ] SKILL.md 总行数 < 300 行
- [ ] 包含验证/检查步骤
### 5. 注册到 003-skills.mdc
将新技能添加到 `.cursor/rules/003-skills.mdc` 的技能目录表中。
## 验证
创建完成后:
1. 确认 `SKILL.md` 存在且格式正确
2.`skillport validate` 或手动检查 YAML frontmatter
3. 用 3 个不同的提示词测试技能是否正确触发

View File

@@ -0,0 +1,149 @@
---
name: vue-page
version: 5.1.0
description: "生成 Vue 3 + Vue Router 页面脚手架,含布局和加载状态。当需要新建路由页面时使用。管理端用 Element Plus用户端用 Headless UI。"
requires: [vue-testing]
---
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-vue-page.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Vue 3 + Vue Router Page Scaffold
## 触发条件
用户要求创建新的页面、路由页面、带有布局的页面。
## 执行流程
### 0. 加载规范
读取 `.cursor/rules/010-typescript.mdc``.cursor/rules/011-vue.mdc``019-modular.mdc`,提取类型注解要求(隐式 any 禁令、ref 泛型规范、Composable 类型规范、script setup、组件分类、拆分阈值、SFC 结构。
### 0.5 ⚠️ 生成前强制拆分分析(禁止跳过)
**在写任何代码之前,必须先输出拆分方案。** 按以下顺序检查:
#### A. 多视图检测
页面是否包含多个"屏"(通过 `v-if / v-else-if` 切换不同视图)?
**判断标准**:同一区域内,通过状态变量控制显示/隐藏的不同内容块每块有独立的表单字段、UI 结构或交互逻辑。
> **规则**:页面内存在 ≥2 个相互排斥的内容视图无论是登录态切换、Tab 内容、步骤表单还是其他形式)→ **每个视图必须是独立组件**,页面文件只做编排和视图切换逻辑。
#### B. 行数预估
基于需求估算各部分代码量:
| 判断标准 | 必须操作 |
|---------|---------|
| 整页 template 预计 > 80 行 | 识别可拆子区域,每个区域 → 独立组件 |
| script 逻辑预计 > 60 行 | 提取为 `use<PageName>.ts` composable**必须遵循 `010-typescript.mdc` Composable 类型规范** |
| 整个 SFC 预计 > 150 行 | **停止**,先完成拆分方案再生成 |
#### C. 重复 UI 模式检测
页面中是否存在相同结构的 UI 块(如多个表单输入项、多个卡片)?
> **规则**:同一 UI 结构出现 ≥3 次 → 必须抽取为基础组件(例:`FormInput.vue`、`CaseCard.vue`
#### D. 输出拆分方案(必须在代码前输出)
格式:
```
src/views/<module>/<page>/
├── index.vue ← 编排层,仅做布局编排和视图切换,目标 ≤ 60 行
├── components/
│ ├── <ViewA>.vue ← 视图A≤ 120 行)
│ ├── <ViewB>.vue ← 视图B≤ 120 行)
│ └── <SharedWidget>.vue ← 共享 UI 组件(≤ 80 行)
└── composables/
└── use<PageName>.ts ← 所有表单状态和提交逻辑(≤ 80 行参数必须有类型注解ref 必须有泛型)
```
**用户确认或 AI 自主判断方案合理后,才开始逐文件生成。**
---
### 1. 确认页面规格
| 字段 | 必填 | 默认值 |
|------|------|--------|
| 路由路径 | ✅ | — |
| 页面标题 | ✅ | — |
| 所属模块 | ✅ | — |
| 需要数据获取? | ❌ | true |
| 需要独立 Layout | ❌ | false |
| 是动态路由? | ❌ | false |
| 需要权限? | ❌ | true |
| 页面类型 | ❌ | list |
类型list | detail | form | dashboard | blank | multi-view新增
### 2. 生成文件结构
按步骤 0.5 输出的拆分方案生成,**所有文件逐一输出,不合并到一个文件**。
入口:`src/views/<module>/<page-name>/index.vue`,路由:`src/router/routes/<module>.ts`
### 3. 页面模板
**index.vue 编排层职责**(强制):
- 只负责:整体布局骨架 + 子组件引用 + 视图切换逻辑
- 禁止在 index.vue 内写具体表单字段、业务 UI 细节
**管理端**列表页useTable、搜索栏、el-table、el-pagination。详情页fetchDetail、el-skeleton、el-descriptions。
**用户端**列表页useTable、搜索栏、自定义 Tailwind 卡片。详情页fetchDetail、骨架屏、自定义描述区禁止 Element Plus
完整模板见 **Tier 3**
### 4. 路由配置
path、name、component 懒加载、metatitle、requiresAuth、permission、icon
### 4.5 Pinia Store 设计
ListItem轻量与 Detail完整分离。detailMap 缓存已加载详情。fetchDetail 有缓存则直接返回。完整 Store 结构见 **Tier 3**
### 4.6 Provider 模式
页面内 ≥3 个子组件共享状态时Provider + keys + 子组件 inject。简单页面直接模板即可。完整实现见 **Tier 3**
### 5. 动态路由 (RBAC)
menuStore.fetchMenus() → router.addRoute。
## 验证
1. [ ] 页面可正常渲染
2. [ ] document.title 正确
3. [ ] Loading 骨架屏
4. [ ] 错误提示友好
5. [ ] meta.requiresAuth、meta.permission
6. [ ] 动态路由参数验证
7. [ ] ESLint 无报错
8. [ ] **index.vue ≤ 60 行**(编排层)
9. [ ] **无子组件超过 150 行**
10. [ ] **多视图已拆分为独立组件**(若适用)
11. [ ] **重复 UI 结构已抽取为基础组件**≥3 次重复时)
12. [ ] **提取的 composableuse*.ts参数有类型注解、`ref([])` / `ref(null)` 有泛型标注**(参见 `010-typescript.mdc` Composable 类型规范)
### Red Flags触发则必须停止并重构
- ❌ 未输出拆分方案直接写代码 → 停止,先输出步骤 0.5 的方案
- ❌ index.vue > 60 行(含 template + script → 拆分子组件
- ❌ 任意 SFC > 150 行 → 拆分
- ❌ 多视图≥2 个 v-if 屏)写在同一文件 → 每个视图独立组件
- ❌ 同一 UI 结构重复 ≥3 次 → 抽取为基础组件
- ❌ Options API → script setup
- ❌ 直接修改 props → emit
- ❌ watch deep:true 滥用
- ❌ ≥3 子组件 prop drilling → Provider
- ❌ 列表返回详情级重型字段 → 分离 ListItem/Detail
- ❌ 详情每次都请求 → detailMap 缓存
- ❌ composable 参数无类型 / `ref([])` 无泛型 → 补全类型注解(`strict: true` 下必报错)
## Tier 3 深度参考
| 文件 | 内容 |
|------|------|
| `references/page-templates.md` | 列表/详情/路由/Pinia/Provider 完整模板 |
| `.cursor/skills/component-scaffold/references/naming-conventions.md` | 页面命名规范 |
| `.cursor/skills/component-scaffold/references/vue-api-reference.md` | Vue 3 API 索引 |

View File

@@ -0,0 +1,54 @@
# Vue Page — 页面模板与 Store
> 主流程见 SKILL.md本文档为列表页/详情页/路由/Pinia/Provider 的完整实现。
>
> **⚠️ 双前端区分**:本文件中使用 `el-*` 组件的模板**仅适用于管理端** (`Case-Database-Frontend-admin/`)。
> 用户端 (`Case-Database-Frontend-user/`) 使用 Headless UI + Tailwind CSS**禁止引入 Element Plus**。
## 列表页模板
```vue
<script setup>
import { useTable } from '@/hooks/useTable'
document.title = '{{PageTitle}} - {{AppName}}'
const { loading, dataList, pagination, loadData } = useTable((params) => {{resource}}Api.list(params))
const searchForm = reactive({ keyword: '', status: '' })
function handleSearch() { pagination.current = 1; loadData() }
onMounted(() => loadData())
</script>
<template>
<div class="p-4 space-y-4">
<el-card><el-form :model="searchForm" inline>...</el-form></el-card>
<el-card>
<template #header><span>{{PageTitle}}</span><el-button @click="handleCreate">新增</el-button></template>
<el-table v-loading="loading" :data="dataList" border stripe>...</el-table>
<el-pagination v-model:current-page="pagination.current" ... @current-change="loadData" />
</el-card>
</div>
</template>
```
## 详情页模板
使用 useRoute/useRouterfetchDetail 加载数据el-skeleton 加载态el-page-header + el-descriptions。
## 路由配置
```typescript
const routes = [
{ path: '/{{module}}/{{route-path}}', name: '{{RouteName}}', component: () => import('@/views/...'), meta: { title, requiresAuth: true, permission } },
{ path: '/{{module}}/{{route-path}}/:id', name: '{{RouteName}}Detail', ... }
]
```
## Pinia Store — List 与 Detail 分离
ListItem 类型:轻量,排除大文本/复杂对象/大数组。Detail 类型完整字段。Storelist (array)、detailMap (Record<id, Detail>)、loadingDetailIds、getDetail(id)、isDetailLoading(id)、fetchList、fetchDetail有缓存则返回、invalidateDetail、invalidateAll。
## 页面级 Provider 模式
当 ≥3 个子组件共享状态时provider.vue provide 状态+actionskeys.ts 定义 PAGE_KEY + usePageContext子组件 inject。文件结构index.vue、provider.vue、keys.ts、components/SearchBar/DataTable/BatchActions、composables/usePageData。
## 动态路由 (RBAC)
loadDynamicRoutesmenuStore.fetchMenus()router.addRoute('layout', { path, name, component: () => import(...), meta })。

View File

@@ -0,0 +1,80 @@
---
name: vue-testing
version: 1.0.0
description: "Vue 3 + Vitest 前端测试策略与工作流。当需要编写单元测试、组件测试或 E2E 测试时使用。涵盖测试决策树和覆盖率标准。"
---
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-vue-testing.mdc` 中由 Cursor 自动注入。
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
# Vue 3 + Vitest 前端测试
## 触发条件
用户要求编写测试、改善测试覆盖率、或询问测试策略。
## 测试决策树(⚠️ 每次写测试前必须先走)
```
纯转换逻辑parse/format/validate/compute→ 提取到 .utils.ts写 Vitest 单元测试
涉及复杂 UI 交互?→ Vue Test Utils 组件测试
能提取为纯函数或 composable→ 提取后写单元测试
否则 → 组件测试
跨页面流程、关键业务路径?→ Playwright E2E
```
## 测试类型与工具
| 类型 | 工具 | 文件命名 |
|---|---|---|
| 单元测试 | Vitest | *.test.ts |
| 组件测试 | Vitest + Vue Test Utils | *.test.ts |
| E2E | Playwright | *.spec.ts |
## 核心原则
1. **逻辑提取** — 业务逻辑提取到 .utils.ts 纯函数,组件薄壳
2. **穷举排列** — 有效/无效/空值/边界/安全输入
3. **AAA 模式** — Arrange-Act-Assert
4. **单一行为** — 每个 it() 只验证一个行为
5. **命名**`should <行为> when <条件>`
## 组件测试结构
Rendering必须、Props必须、User Interactions、Edge Cases必须。完整模板见 **Tier 3**
## 增量式工作流(必须)
工具函数 → Composables → 简单组件 → 中等 → 复杂 → 集成。逐个文件写测试→vitest run→PASS 才下一个。
## 复杂度阈值
< 30 标准结构。3050 按 describe 分组。> 50 先重构再测试。500+ 行强制考虑拆分。
## Mock 策略
API/Router Mock。Pinia 用真实 Store。管理端 Element Plus 不 Mock用户端不涉及 Element Plus使用 Headless UI。子组件不 Mock。完整策略见 **Tier 3**
## 覆盖率目标
Function 100%、Statement 100%、Branch > 95%、Line > 95%。
## 验证
1. [ ] 走过决策树
2. [ ] 逻辑已提取到 .utils.ts
3. [ ] 穷举排列覆盖
4. [ ] 每测试单行为
5. [ ] 命名 should...when...
6. [ ] Mock 正确
7. [ ] 增量式工作流
8. [ ] 覆盖率达标
9. [ ] vitest run 全部通过
## Tier 3 深度参考
| 文件 | 内容 |
|------|------|
| `references/testing-templates.md` | 组件/Composable 测试模板、Mock 策略、增量工作流 |
| `references/vitest-config.md` | Vitest + Vue 3 配置 |
| `references/mock-patterns.md` | Mock 模式速查 |

View File

@@ -0,0 +1,31 @@
# Mock Patterns Quick Reference
## API Mock
```js
import { vi } from 'vitest'
import * as userApi from '@/api/user'
vi.spyOn(userApi, 'getUser').mockResolvedValue({ id: 1, name: 'demo' })
```
## Router Mock
```js
const push = vi.fn()
vi.mock('vue-router', () => ({
useRouter: () => ({ push }),
useRoute: () => ({ params: { id: '1' } })
}))
```
## Pinia
- 默认优先真实 store`setActivePinia(createPinia())`
- 仅在外部依赖复杂且非本次关注点时 mock action
## 禁止过度 Mock
- 不要 mock Vue 内置行为
- 不要 mock 被测模块本身
- 避免因为 mock 过多导致测试与真实行为偏离

View File

@@ -0,0 +1,79 @@
# Vue Testing — 测试模板与策略
> 主流程见 SKILL.md本文档为组件测试、Composable 测试、Mock 策略的完整代码。
>
> **⚠️ 双前端区分**:管理端测试需注册 Element Plus 插件,用户端测试**不注册 Element Plus**。
## 组件测试模板
```typescript
import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
// 管理端import ElementPlus from 'element-plus'
// 用户端:不引入 Element Plus
import ComponentName from './ComponentName.vue'
describe('ComponentName', () => {
beforeEach(() => setActivePinia(createPinia()))
describe('Rendering', () => {
it('should render without crashing', () => {
// 管理端global: { plugins: [ElementPlus] }
// 用户端global: {}(无 Element Plus
const wrapper = mount(ComponentName, { props: { title: 'Test' } })
expect(wrapper.find('[data-testid="component-name"]').exists()).toBe(true)
})
})
describe('Props', () => {
it('should display title from props', () => { ... })
})
describe('User Interactions', () => {
it('should emit refresh when button clicked', async () => { ... })
})
describe('Edge Cases', () => {
it('should handle empty props gracefully', () => { ... })
})
})
```
## Composable 测试模板
```typescript
import { useCounter } from './useCounter'
describe('useCounter', () => {
it('should initialize with default value', () => { const { count } = useCounter(); expect(count.value).toBe(0) })
it('should increment count', () => { const { count, increment } = useCounter(); increment(); expect(count.value).toBe(1) })
it('should not go below zero', () => { ... })
})
```
## 逻辑提取示例
```typescript
// OrderStatus.utils.ts
export function getStatusText(status, shipped) {
if (status === 'paid' && shipped) return '已发货'
if (status === 'paid') return '待发货'
if (status === 'refunded') return '已退款'
return '待支付'
}
// OrderStatus.utils.test.ts — 穷举排列:有效/无效/空值/边界
```
## Mock 策略
| 依赖 | Mock? | 方式 |
|------|-------|------|
| @/api/* | ✅ | vi.mock('@/api/xxx') |
| vue-router | ✅ | vi.mock('vue-router') |
| Pinia | ⚠️ 真实 | setActivePinia(createPinia()) |
| Element Plus | ❌ | 全局 plugin |
| 子组件 | ❌ | 直接导入 |
| window/document | ✅ | vi.stubGlobal |
## 增量式工作流
1. 工具函数 → Composables → 简单组件 → 中等组件 → 复杂组件 → 集成。2. 每个文件:写测试→运行 vitest→PASS 才下一个。3. 最终 vitest run --coverage。

View File

@@ -0,0 +1,39 @@
# Vitest Configuration (Vue 3)
## 最小配置
```js
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./tests/setup.ts'],
coverage: {
reporter: ['text', 'html'],
include: ['src/**/*.{js,vue}']
}
}
})
```
## setup 示例
```js
// tests/setup.ts
import { config } from '@vue/test-utils'
config.global.mocks = {
$t: (k) => k
}
```
## 建议
- 单元测试优先针对 `.utils.ts` 与 composables
- 组件测试只覆盖关键交互与边界状态
- 覆盖率门槛与业务关键性一致,不盲目追求 100%

View File

@@ -0,0 +1,53 @@
---
name: websocket-service
version: 1.0.0
description: "在 Hyperf 中搭建 Swoole WebSocket 服务器。当需要实时通信、消息推送或通知功能时使用。含心跳检测和前端集成。"
---
# Swoole WebSocket Service
## 触发条件
用户需要实时通信功能:通知推送、在线状态、实时数据更新、聊天功能。
## 执行流程
### Phase 0: 加载规范
读取 `.cursor/rules/016-swoole.mdc``013-backend.mdc`,提取 WebSocket 配置、协程安全、fd 存储、心跳、认证鉴权。
### Phase 1: 服务端配置
`config/autoload/server.php` 添加 ws 服务type SERVER_WEBSOCKETport 环境变量callbacks ON_HAND_SHAKE/ON_MESSAGE/ON_CLOSE。
### Phase 2: 连接管理器
WebSocketConnectionManagerRedis 存储 fd→userId/serverIduserId→{serverId:fd} 集合。addConnection、removeConnection、getUserConnections、getUserIdByFd、isOnline。支持多服务器。
### Phase 3: WebSocket Controller
OnOpenquery token 验证 JWT无效 closeaddConnectionpush connected。OnMessageping→pong。OnCloseremoveConnection。
### Phase 4: 消息推送服务
WebSocketPushServicepushToUser、pushToUsers、broadcastRedis publish 跨服。Inject Sender、ConnectionManager。
### Phase 5: 前端客户端
WebSocketClientconnect(url?token=)、on(type, callback)、心跳 30s、断线指数退避重连。完整实现见 **Tier 3**
## 验证
1. [ ] WebSocket 在配置端口启动
2. [ ] Token 认证成功才连接
3. [ ] 无效 Token 立即断开
4. [ ] 心跳 30s 正常
5. [ ] 断线自动重连
6. [ ] 连接信息存 Redis
7. [ ] 推送正确到达目标用户
## Tier 3 深度参考
| 文件 | 内容 |
|------|------|
| `references/ws-implementation.md` | 配置、ConnectionManager、Controller、PushService、前端 Client 完整代码 |

View File

@@ -0,0 +1,45 @@
# WebSocket Service — 实现细节
> 主流程见 SKILL.md本文档为服务端配置、连接管理器、Controller、推送服务、前端客户端的完整代码。
## 服务端配置
```php
// config/autoload/server.php
[
'name' => 'ws',
'type' => Server::SERVER_WEBSOCKET,
'host' => '0.0.0.0',
'port' => (int) env('WEBSOCKET_PORT', 9502),
'callbacks' => [
Event::ON_HAND_SHAKE => [Hyperf\WebSocketServer\Server::class, 'onHandShake'],
Event::ON_MESSAGE => [...],
Event::ON_CLOSE => [...],
],
]
```
## 连接管理器
WebSocketConnectionManagerRedis HASH `ws:conn:{fd}` 存 user_id/server_id/connected_at。Redis SET `ws:user:{userId}``{serverId}:{fd}`支持多连接。addConnection、removeConnection、getUserConnections、getUserIdByFd、isOnline。
## WebSocket Controller
OnOpenInterface从 query token 验证 JWT无效则 close。addConnection。push connected 消息。OnMessageInterface解析 typeping→pong。OnCloseInterfaceremoveConnection。validateToken 返回 userId。
## 消息推送服务
WebSocketPushServicepushToUser(userId, type, data) 遍历 getUserConnectionsSender->push。pushToUsers 循环 pushToUser。broadcast 用 Redis publish 跨服务器。
## 前端 WebSocketClient
```typescript
class WebSocketClient {
constructor(url, token) {}
connect() { this.ws = new WebSocket(`${url}?token=${token}`) }
onmessage emit(type, data)
on(type, callback) unsubscribe
startHeartbeat() 30s ping
onclose scheduleReconnect 退
}
```