爪子的个人博客

记录生活,分享技术

背景

作为前端工程师,我一直想拓展后端能力。PHP 是最容易入门的服务端语言之一,语法和 JavaScript 相似,学习曲线平缓。用一个下午的时间,我从零学完了 PHP 基础,并进阶到前后端分离 API。

学习路径

1
PHP 基础语法 → 表单处理 → PDO + MySQL → RESTful API → CORS → Token 认证 → MVC 分层

PHP 基础:和 JavaScript 的对比

JS PHP 说明
let name = "test" $name = "test" PHP 变量必须 $ 前缀
str + str str . str PHP 用 . 拼接字符串
=== === PHP 也有严格相等
obj.prop $obj->prop PHP 对象用 ->
arr.push(x) $arr[] = x PHP 数组追加语法
foreach foreach ($arr as $item) 类似但需 $

关键认知转变:PHP 是请求级生命周期——每次请求开始时变量都是空的,请求结束后进程就退出(不像 Node.js 常驻内存)。

PDO + MySQL:安全的数据库操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 连接数据库
function db(): PDO {
static $pdo = null;
if ($pdo === null) {
$pdo = new PDO(
"mysql:host=127.0.0.1;dbname=learnphp;charset=utf8mb4",
"root", "password"
);
}
return $pdo;
}

// 预处理语句防 SQL 注入
$stmt = db()->prepare("SELECT * FROM messages WHERE name LIKE ?");
$stmt->execute(["%$search%"]);
$messages = $stmt->fetchAll();

预处理语句是把 SQL 模板和数据分开发送,数据库先编译模板,再填入参数。参数不会被当作 SQL 执行,彻底杜绝 SQL 注入。

RESTful API 设计

方法 路径 含义
GET /messages 获取列表
POST /messages 创建留言
DELETE /messages?id=1 删除留言(需认证)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 统一 JSON 响应
function jsonOut(int $code, string $message, $data = null, int $http = 200): void {
http_response_code($http);
echo json_encode([
"code" => $code,
"message" => $message,
"data" => $data
]);
exit;
}

// 获取 JSON 请求体
function inputJson(): array {
return json_decode(file_get_contents("php://input"), true) ?? [];
}

CORS:跨域请求处理

前端和 API 不同域名时,浏览器会发 CORS 预检(OPTIONS 请求)。简单请求(GET/POST 无自定义头)直接通过,复杂请求需要预检。

1
2
3
4
5
6
7
8
9
10
function handleCORS(): void {
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");

if ($_SERVER["REQUEST_METHOD"] === "OPTIONS") {
http_response_code(204);
exit;
}
}

Bearer Token 认证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function requireAuth(): array {
$header = $_SERVER["HTTP_AUTHORIZATION"] ?? "";
if (!preg_match("/^Bearer\s+(.+)$/", $header, $m)) {
jsonOut(1, "未登录", null, 401);
}
$token = $m[1];

$stmt = db()->prepare(
"SELECT id, username FROM users WHERE token = ? AND token_expires_at > NOW()"
);
$stmt->execute([$token]);
$user = $stmt->fetch();
if (!$user) {
jsonOut(2, "token 无效或过期", null, 401);
}
return $user;
}

登录时用 random_bytes(32) 生成 64 字符随机 token,存到 users 表。这种 opaque token 比 JWT 更适合学习场景——简单直观,不引入额外库。

密码存储用 bcrypt:

1
2
$hash = password_hash($password, PASSWORD_DEFAULT);  // 注册时
password_verify($password, $user["password_hash"]); // 登录时验证

MVC 分层重构

从单文件 API 重构到 MVC 分层:

1
2
3
4
5
6
7
8
9
api/v2/
├── index.php # 前端控制器 + 路由表
├── models/
│ ├── Message.php # 数据层(SQL)
│ └── User.php
├── controllers/
│ ├── MessageController.php # 业务层(HTTP + 规则)
│ └── UserController.php
└── common.php # 公共工具(DB/CORS/jsonOut)

Model 层只管 SQL,不碰 HTTP。Controller 层处理请求解析、业务校验、响应格式。

1
2
3
4
5
6
7
8
9
// 路由表(前端控制器)
$routes = [
"messages" => [
"GET" => [$controller, "index"],
"POST" => [$controller, "store"],
"DELETE" => [$controller, "destroy"],
],
];
($routes[$path][$method])(); // 分发

依赖注入:从外层组装对象图,传入内部:

1
2
$messageModel = new Message(db());
$controller = new MessageController($messageModel);

这让你理解了 Laravel 的核心概念——Eloquent ORM、路由文件、Controller、中间件、服务容器,都是你手写原型的标准化版本。

总结

前端工程师学 PHP 的优势:

  • 语法相似度高($ 前缀、. 拼接是最大差异)
  • HTTP 概念相通(请求/响应/状态码/Header)
  • MVC 分层思想和其他框架一致
  • 异步思维转变后,同步阻塞模型反而更简单

最难的不是语言本身,而是服务端思维:请求级生命周期、并发处理、进程管理。但这些理解后,PHP 就是最直观的后端入门选择。

本文记录 Laravel API 开发的核心知识点,涵盖认证、数据库关系、中间件、API 资源、分页、表单验证、异常处理等模块。

一、Sanctum 认证系统

1.1 Token 格式理解

Token 格式为 1|abc123...

  • 1 = personal_access_tokens 表的 ID
  • abc123... = 明文 token(客户端保存)
  • 数据库只存哈希值 hash('sha256', 'abc123...')

1.2 验证流程

1
2
3
4
5
6
7
客户端发送:1|abc123...

框架解析:id=1, plainText=abc123...

查数据库:SELECT * FROM personal_access_tokens WHERE id=1

比对哈希:hash('sha256', 'abc123...') === 数据库的 token?

1.3 核心代码

注册:

1
2
3
4
5
$user = User::create([
'username' => $validated['username'],
'password' => $validated['password'], // Model 自动哈希
]);
$token = $user->createToken('api')->plainTextToken;

登录:

1
2
3
4
if (!Auth::attempt($credentials)) {
return ['message' => '用户名或密码错误'];
}
$token = $request->user()->createToken('api')->plainTextToken;

登出(当前设备):

1
$request->user()->currentAccessToken()->delete();

登出(所有设备):

1
$request->user()->tokens()->delete();

1.4 路由保护

1
2
3
Route::middleware('auth:sanctum')->group(function () {
Route::post('/messages', [MessageController::class, 'store']);
});

1.5 获取当前用户

1
$request->user()->id

1.6 关键点

要点 说明
密码哈希 Model 的 $casts = ['password' => 'hashed'] 自动处理
Token 返回 $token->plainTextToken(不是 $token
多设备 每个设备独立 token,登出只删当前设备
哈希存储 数据库不存明文,防止泄露后被直接使用

二、数据库关系

2.1 一对多关系

User Model:

1
2
3
4
public function messages()
{
return $this->hasMany(Message::class);
}

Message Model:

1
2
3
4
public function user()
{
return $this->belongsTo(User::class);
}

2.2 预加载(避免 N+1)

1
2
3
4
5
6
7
8
// 不用预加载(N+1 问题)
$messages = Message::all();
foreach ($messages as $message) {
$message->user; // 每条留言都查一次用户
}

// 用预加载(只查 2 次)
$messages = Message::with('user')->get();

2.3 关联用户

数据库添加字段:

1
2
3
ALTER TABLE messages ADD COLUMN user_id BIGINT UNSIGNED NULL;
ALTER TABLE messages ADD CONSTRAINT fk_user
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;

创建时自动关联:

1
2
$validated['user_id'] = $request->user()->id;
$message = Message::create($validated);

三、中间件权限控制

3.1 创建中间件

1
php artisan make:middleware CheckMessageOwner

3.2 中间件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function handle(Request $request, Closure $next): Response
{
$id = $request->query('id');
$message = Message::find($id);

if (!$message) {
return response()->json(['code' => 404, 'msg' => '留言不存在'], 404);
}

if ($message->user_id !== $request->user()->id) {
return response()->json(['code' => 403, 'msg' => '无权删除'], 403);
}

return $next($request);
}

3.3 注册中间件别名(Laravel 11)

1
2
3
4
5
6
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
$middleware->alias([
'check-message-owner' => CheckMessageOwner::class,
]);
})

3.4 路由应用中间件

1
2
Route::delete('/messages', [MessageController::class, 'destroy'])
->middleware('check-message-owner');

四、API 资源

4.1 创建资源类

1
2
php artisan make:resource UserResource
php artisan make:resource MessageResource

4.2 定义返回字段

UserResource:

1
2
3
4
5
6
7
8
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'username' => $this->username,
// 只返回需要的字段,隐藏 email_verified_at、updated_at 等
];
}

MessageResource(嵌套资源):

1
2
3
4
5
6
7
8
9
10
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'content' => $this->content,
'created_at' => $this->created_at,
'user' => new UserResource($this->user), // 嵌套
];
}

4.3 使用资源

单个资源:

1
return new MessageResource($message);

资源集合:

1
return MessageResource::collection($messages);

五、分页

5.1 使用 paginate()

1
$messages = Message::latest()->with('user')->paginate($perPage);

5.2 返回分页数据

1
2
3
4
5
return MessageResource::collection($messages)
->additional([
'code' => 0,
'msg' => 'ok',
]);

5.3 返回结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"data": [...],
"links": {
"first": "...",
"last": "...",
"prev": null,
"next": "..."
},
"meta": {
"current_page": 1,
"last_page": 4,
"per_page": 10,
"total": 19
},
"code": 0,
"msg": "ok"
}

5.4 URL 参数

1
GET /api/messages?page=2&per_page=10

六、表单验证类(Form Request)

6.1 创建 Form Request

1
php artisan make:request StoreMessageRequest

6.2 定义验证规则

1
2
3
4
5
6
7
8
9
10
11
12
13
public function authorize(): bool
{
return true; // 允许请求
}

public function rules(): array
{
return [
'name' => 'required|max:20',
'email' => 'nullable|email',
'content' => 'required',
];
}

6.3 Controller 使用

1
2
3
4
5
6
7
public function store(StoreMessageRequest $request)
{
$validated = $request->validated(); // 获取已验证数据
$validated['user_id'] = $request->user()->id;
$message = Message::create($validated);
// ...
}

6.4 对比原生 PHP

原生 PHP Laravel Form Request
Controller 里写验证 独立 Request 类
$request->validate([...]) $request->validated()
规则分散 集中管理
不可复用 可复用

七、异常处理(统一错误格式)

7.1 配置位置

1
2
3
4
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions): void {
// ...
})

7.2 处理验证异常(422)

1
2
3
4
5
6
7
8
9
10
11
use Illuminate\Validation\ValidationException;

$exceptions->render(function (ValidationException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'code' => 422,
'msg' => $e->getMessage(),
'errors' => $e->errors(),
], 422);
}
});

7.3 处理认证异常(401)

1
2
3
4
5
6
7
8
9
10
use Illuminate\Auth\AuthenticationException;

$exceptions->render(function (AuthenticationException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'code' => 401,
'msg' => '无权限访问',
], 401);
}
});

7.4 统一错误格式

错误类型 返回格式 HTTP 状态码
验证失败 {"code":422,"msg":"...","errors":{...}} 422
未登录 {"code":401,"msg":"无权限访问"} 401
无权限 {"code":403,"msg":"无权删除"} 403
资源不存在 {"code":404,"msg":"留言不存在"} 404

八、常用命令汇总

命令 用途
php artisan make:request XxxRequest 创建表单验证类
php artisan make:middleware XxxMiddleware 创建中间件
php artisan make:resource XxxResource 创建 API 资源
php artisan test 运行测试
php artisan vendor/bin/pint 修复代码风格
php artisan migrate 执行迁移
php artisan migrate:rollback 回滚迁移
php artisan tinker REPL 交互环境

九、对比原生 PHP

功能 原生 PHP Laravel
Token 生成 bin2hex(random_bytes(32)) $user->createToken('api')
Token 验证 手写 SQL 查询比对 auth:sanctum 中间件
关系查询 手写 JOIN SQL with('user') 预加载
验证规则 手写 if 判断 Form Request 类
分页 手写 LIMIT + COUNT paginate() 自动处理
错误处理 各种格式混用 统一 JSON 格式

十、项目文件结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
learn-laravel/
├── app/
│ ├── Http/
│ │ ├── Controllers/ # 业务逻辑
│ │ ├── Middleware/ # 中间件
│ │ ├── Requests/ # 表单验证类
│ │ └── Resources/ # API 资源
│ └── Models/ # Eloquent Model
├── bootstrap/
│ └── app.php # 异常处理配置
├── routes/
│ └── api.php # API 路由
└── database/
└── migrations/ # 数据库迁移

参考资料

记录 Laravel API 开发过程中的关键知识点和常见陷阱,帮助你避免踩坑。

项目背景

技术栈:

  • Laravel 11
  • Laravel Sanctum(API Token 认证)
  • MySQL 数据库
  • PHPUnit 测试

项目功能:

  • 用户注册、登录、登出
  • 任务 CRUD(创建、列表、删除)
  • API Token 认证
  • 分页查询

核心知识点

Laravel Sanctum 认证机制

1. 安装与配置

1
2
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

2. User Model 必须引入 HasApiTokens trait

❌ 错误示例:

1
2
3
4
5
class User extends Authenticatable
{
use HasFactory, Notifiable;
// 缺少 HasApiTokens
}

调用 createToken() 时会报错:Call to undefined method App\Models\User::createToken()

✅ 正确写法:

1
2
3
4
5
6
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}

原因: createToken() 方法定义在 HasApiTokens trait 中,必须显式引入。

3. Token 认证流程

注册时创建 Token:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function register(UserRequest $request)
{
$user = User::create($validated);
$token = $user->createToken('api-token')->plainTextToken;

return response()->json([
'code' => 0,
'msg' => '注册成功',
'data' => [
'user' => new UserResource($user),
'token' => $token,
],
]);
}

登出时删除 Token:

1
2
3
4
5
6
7
8
9
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();

return response()->json([
'code' => 0,
'msg' => '登出成功',
]);
}

Form Request 验证

1. 创建 Form Request

1
2
3
php artisan make:request UserRequest
php artisan make:request LoginRequest
php artisan make:request TaskRequest

2. Controller 中使用

1
2
3
4
5
6
7
8
9
10
11
public function store(TaskRequest $request)
{
// 验证通过后,通过 $request->validated() 获取验证后的数据
$task = $request->user()->tasks()->create($request->validated());

return response()->json([
'code' => 0,
'msg' => '创建成功',
'data' => new TaskResource($task),
]);
}

好处:

  • 验证逻辑与 Controller 分离
  • 自动返回 422 错误响应
  • 代码更清晰、易维护

Resource 资源类

1. 定义数据格式

1
2
3
4
5
6
7
8
9
10
11
12
13
class TaskResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'description' => $this->description,
'completed' => (bool) $this->completed, // 强制转换布尔值
'created_at' => $this->created_at->toIso8601String(), // 统一时间格式
];
}
}

2. Controller 中使用

单个资源:

1
2
3
4
5
return response()->json([
'code' => 0,
'msg' => '创建成功',
'data' => new TaskResource($task),
]);

资源集合(列表):

1
2
3
4
5
6
7
$tasks = auth()->user()->tasks()->paginate(5);

return TaskResource::collection($tasks)
->additional([
'code' => 0,
'msg' => '获取任务列表成功',
]);

Eloquent 关系查询

1. Model 关系定义

1
2
3
4
5
6
7
8
9
10
11
// User.php
public function tasks()
{
return $this->hasMany(Task::class);
}

// Task.php
public function user()
{
return $this->belongsTo(User::class);
}

2. 使用关系创建任务

✅ 推荐写法:

1
2
3
4
5
6
7
8
9
10
public function store(TaskRequest $request)
{
$task = $request->user()->tasks()->create($request->validated());

return response()->json([
'code' => 0,
'msg' => '创建成功',
'data' => new TaskResource($task),
]);
}

好处:

  • 自动设置 user_id
  • 代码更简洁
  • 符合 Laravel 最佳实践

3. 查询当前用户的任务

✅ 正确写法:

1
2
3
4
5
6
7
8
9
10
public function index(Request $request)
{
$perPage = $request->query('per_page', 5);
$tasks = Task::where('user_id', $request->user()->id)
->latest()
->paginate($perPage);

return TaskResource::collection($tasks)
->additional(['code' => 0, 'msg' => '获取任务列表成功']);
}

或者使用关系查询:

1
$tasks = $request->user()->tasks()->latest()->paginate($perPage);

API 响应格式设计

1. 统一响应格式

场景 响应格式
成功(无数据) {"code": 0, "msg": "操作成功"}
成功(有数据) {"code": 0, "msg": "获取成功", "data": {...}}
成功(列表) {"data": [...], "meta": {...}, "code": 0, "msg": "获取成功"}
验证错误 {"code": 422, "msg": "错误信息", "errors": {...}}
认证失败 {"code": 401, "msg": "未认证"}

2. 关键问题:code 类型必须统一

前端判断:if (data.code === 0) 使用严格相等

❌ 错误: 返回字符串 "0" —— "0" === 0 返回 false

✅ 正确: 返回数字 0

3. 关键问题:key 名称必须统一

❌ 错误: 后端返回 message,前端期望 msg

✅ 正确: 统一使用 msg

常见错误与解决方案

错误 1:Call to undefined method User::createToken()

原因: User Model 缺少 HasApiTokens trait。

解决方案:

1
2
3
4
5
6
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}

错误 2:用户 A 能看到用户 B 的任务

原因: 查询时未过滤 user_id

解决方案:

1
$tasks = Task::where('user_id', $request->user()->id)->paginate(5);

错误 3:where 条件误用 like

❌ 错误:

1
Task::where('user_id', 'like', $request->user()->id)

原因: like 用于字符串模糊匹配,user_id 是整数。

✅ 正确:

1
Task::where('user_id', $request->user()->id)

错误 4:删除任务时访问 null 属性报错

✅ 正确顺序: 先检查是否存在,再检查权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function destroy(Request $request, string $id)
{
$task = Task::find($id);

// 先检查是否存在
if (!$task) {
return response()->json(['code' => 404, 'msg' => '任务不存在'], 404);
}

// 再检查权限
if ($task->user_id != $request->user()->id) {
return response()->json(['code' => 403, 'msg' => '无权操作'], 403);
}

$task->delete();

return response()->json(['code' => 0, 'msg' => '删除成功']);
}

最佳实践总结

Laravel API 开发的关键在于:

  1. 认证机制:理解 Sanctum 的工作原理,正确配置 HasApiTokens trait
  2. 响应格式:统一 code 类型和 key 名称,避免前端判断失败
  3. 数据查询:正确使用 Eloquent 关系,确保只查询当前用户的数据
  4. 异常处理:配置 API 异常处理,返回规范的 JSON 响应
  5. 代码规范:方法命名统一、引入必要依赖、逻辑顺序正确

遵循这些最佳实践,可以避免 90% 的常见错误,写出高质量的 Laravel API。

参考资料

什么是 Hermes?

Hermes 是一个强大的 AI Agent 开发框架,专为构建企业级 AI 助手而设计。它提供了:

  • 🤖 多 Agent 协作能力
  • 🔧 丰富的工具集成
  • 💾 持久化记忆系统
  • 🎯 技能系统(Skills)
  • 📊 任务管理和调度
  • 🔌 多平台适配(CLI、Web、IM)

核心架构

1. Agent 架构

1
2
3
4
5
6
7
Hermes Agent
├── Core Engine # 核心引擎
├── Tool System # 工具系统
├── Memory System # 记忆系统
├── Skill System # 技能系统
├── Session Manager # 会话管理
└── Platform Adapter # 平台适配

2. 工具系统

Hermes 提供丰富的内置工具:

工具集 功能
browser 浏览器操作(导航、截图、交互)
terminal 命令行执行、脚本运行
file 文件读写、搜索、编辑
web 网络请求、API 调用
search 搜索引擎、信息检索
vision 图像识别、视觉理解
cronjob 定时任务、调度管理

快速开始

安装配置

1
2
3
4
5
6
7
8
9
# 克隆项目
git clone https://github.com/nousresearch/hermes.git
cd hermes

# 安装依赖
npm install

# 配置
cp config.example.yaml config.yaml

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# config.yaml
model:
provider: openrouter
model: anthropic/claude-sonnet-4

providers:
openrouter:
api_key: ${OPENROUTER_API_KEY}
base_url: https://openrouter.ai/api/v1

agents:
defaults:
tools:
- browser
- terminal
- file
- web
max_tokens: 4096
temperature: 0.7

启动 Agent

1
2
3
4
5
6
7
8
# CLI 模式
hermes

# 指定配置
hermes --config config.yaml

# Web 模式
hermes web --port 3000

实战案例

案例 1:构建开发助手

创建一个能帮助开发的 Agent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# skills/dev-assistant/SKILL.md

---
name: dev-assistant
description: AI 开发助手,帮助代码开发和调试
triggers:
- 开发
- 写代码
- debug
---

# 开发助手

## 能力
1. 代码生成和优化
2. 代码审查
3. Bug 调试
4. 测试生成
5. 文档编写

## 工作流程

### 代码生成
1. 理解需求
2. 设计方案
3. 生成代码
4. 运行测试
5. 优化改进

### Bug 调试
1. 分析错误信息
2. 定位问题代码
3. 提出修复方案
4. 实施修复
5. 验证修复

使用

1
2
3
4
5
6
7
8
9
# 用户:帮我创建一个用户登录 API
hermes> 帮我创建一个用户登录 API

# Agent 会自动:
# 1. 创建 API 接口
# 2. 添加参数验证
# 3. 实现认证逻辑
# 4. 生成测试代码
# 5. 编写 API 文档

案例 2:多 Agent 协作

使用 delegate_task 创建多 Agent 协作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 代码审查流程
from hermes import Agent, Task

# 创建专家 Agent
architect = Agent(
role="架构师",
goal="审查架构设计",
tools=["file", "search"]
)

reviewer = Agent(
role="代码审查员",
goal="检查代码质量",
tools=["file", "search"]
)

tester = Agent(
role="测试工程师",
goal="验证测试覆盖",
tools=["terminal", "file"]
)

# 分配任务
tasks = [
Task(
description="审查项目架构",
agent=architect
),
Task(
description="检查代码质量",
agent=reviewer
),
Task(
description="验证测试覆盖",
agent=tester
)
]

# 执行
result = delegate_task(tasks=tasks)

案例 3:创建自动化工作流

使用 Cronjob 创建定时任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建日报生成任务
cronjob(
action='create',
name='daily-report',
schedule='0 9 * * *', # 每天早上 9 点
prompt="""
生成今日工作日报:
1. 统计 Git 提交记录
2. 分析任务完成情况
3. 生成进度报告
4. 发送到团队群
""",
skills=['git-analyzer', 'report-generator'],
deliver='telegram:team_group'
)

技能系统(Skills)

创建自定义技能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# skills/code-review/SKILL.md

---
name: code-review
description: 代码审查技能
triggers:
- 审查代码
- code review
---

# 代码审查流程

## 触发条件
用户请求审查代码

## 执行步骤

### 1. 收集代码
使用 `search_files` 搜索需要审查的文件

### 2. 静态分析
- 检查代码规范
- 检查潜在 Bug
- 检查安全问题

### 3. 生成报告
输出格式化的审查报告

## 输出模板

```markdown
# 代码审查报告

## 概览
- 文件数:X
- 问题数:Y
- 严重程度:高/中/低

## 问题列表

### 文件:src/api/users.js
- ⚠️ Line 45: 潜在的 SQL 注入风险
- 💡 Line 78: 建议使用异步方法
- ✅ Line 92: 良好的错误处理

## 改进建议
...
1
2
3
4
5
6
7
8
9
10

### 使用技能

```bash
# 方式一:直接调用
hermes> 使用 code-review 技能审查 src/api 目录

# 方式二:自动触发
hermes> 审查一下最近的代码改动
# Agent 会自动识别并加载 code-review 技能

记忆系统

用户记忆(User Memory)

存储用户偏好和习惯:

1
2
3
4
5
memory(
action='add',
target='user',
content='用户偏好简洁的回复风格'
)

会话记忆(Session Memory)

自动保存会话历史:

1
2
3
4
5
# 搜索历史会话
session_search(
query="讨论过的架构方案",
limit=3
)

持久化技能(Skill Memory)

保存可复用的程序:

1
2
3
4
5
6
# 创建技能
skill_manage(
action='create',
name='api-generator',
content='快速生成 REST API 的模板'
)

平台适配

Telegram Bot

1
2
3
4
5
6
7
# config.yaml
platforms:
telegram:
enabled: true
bot_token: ${TELEGRAM_BOT_TOKEN}
allowed_users:
- 123456789

Discord Bot

1
2
3
4
5
6
7
platforms:
discord:
enabled: true
bot_token: ${DISCORD_BOT_TOKEN}
channels:
- "engineering"
- "general"

Web Interface

1
2
3
4
5
# 启动 Web 界面
hermes web --port 3000

# 访问
open http://localhost:3000

高级功能

1. 子任务委托

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
delegate_task(
tasks=[
{
"goal": "分析用户需求",
"toolsets": ["web", "search"]
},
{
"goal": "设计技术方案",
"toolsets": ["file", "search"]
},
{
"goal": "实现代码",
"toolsets": ["terminal", "file"]
}
],
role="orchestrator" # 编排者模式
)

2. 流式执行

1
2
3
4
5
6
# 实时输出执行过程
result = agent.run(
prompt="重构整个项目",
stream=True,
on_progress=lambda x: print(x)
)

3. 上下文压缩

1
2
3
4
5
6
agents:
defaults:
compaction:
enabled: true
reserveTokensFloor: 20000
strategy: summarize

最佳实践

1. 技能设计原则

  • 单一职责:每个技能专注一件事
  • 可复用:设计通用的技能模板
  • 可测试:包含验证步骤
  • 文档化:清晰的使用说明

2. 工具使用规范

1
2
3
4
5
6
7
8
9
10
11
12
13
# ✅ 好的实践
# 明确指定工具
Agent(
tools=["file", "search"], # 只启用需要的工具
...
)

# ❌ 不好的实践
# 启用所有工具(性能差、成本高)
Agent(
tools="all",
...
)

3. 错误处理

1
2
3
4
5
6
7
8
9
try:
result = agent.run(prompt)
except TokenLimitExceeded:
# 压缩上下文
agent.compact()
result = agent.run(prompt)
except ToolExecutionError as e:
# 回退或重试
logger.error(f"Tool error: {e}")

性能优化

1. Token 优化

  • 使用 reserveTokensFloor 保留缓冲
  • 定期压缩上下文
  • 使用 .hermesignore 排除大文件

2. 并发控制

1
2
3
delegation:
max_concurrent_children: 3 # 最多 3 个并发子任务
max_spawn_depth: 1 # 限制嵌套深度

3. 缓存策略

1
2
3
4
cache:
enabled: true
ttl: 3600 # 1 小时
maxSize: 100MB

与 CrewAI 对比

特性 Hermes CrewAI
企业级 ✅ 完整框架 ⚠️ 需要自己扩展
记忆系统 ✅ 内置 ⚠️ 需要配置
技能系统 ✅ 强大 ❌ 无
多平台 ✅ 支持 ❌ 无
工具数量 50+ 20+
学习曲线 中等 简单

总结

Hermes 是构建企业级 AI Agent 的最佳选择:

  • ✅ 开箱即用的完整方案
  • ✅ 强大的工具和技能系统
  • ✅ 灵活的多 Agent 协作
  • ✅ 持久化记忆和会话管理
  • ✅ 多平台适配

推荐资源


下一步:尝试用 Hermes 构建你的第一个 AI Agent!

前言

在 AI 辅助开发的时代,Claude Code 正在重新定义我们写代码的方式。作为一个强大的 AI 编程助手,它不仅能生成代码,更能理解上下文、重构项目、甚至完成复杂的开发任务。

什么是 Claude Code?

Claude Code 是 Anthropic 推出的命令行 AI 编程助手,基于 Claude 模型。它能够:

  • ✅ 理解整个项目结构
  • ✅ 生成高质量代码
  • ✅ 重构和优化代码
  • ✅ 编写测试用例
  • ✅ 解释复杂逻辑

安装与配置

安装

1
2
3
4
5
# macOS/Linux
curl -fsSL https://claude.ai/install.sh | sh

# 或使用 npm
npm install -g @anthropic/claude-code

配置 API Key

1
2
3
4
5
# 设置环境变量
export ANTHROPIC_API_KEY="your-api-key-here"

# 或在项目根目录创建 .env 文件
echo "ANTHROPIC_API_KEY=your-api-key" > .env

实战案例

案例 1:快速创建 REST API

场景:需要创建一个用户管理的 REST API

Prompt

1
2
3
4
5
6
7
创建一个用户管理 API,包含以下功能:
- GET /users - 获取用户列表
- POST /users - 创建用户
- PUT /users/:id - 更新用户
- DELETE /users/:id - 删除用户

使用 Express.js,包含参数验证和错误处理

Claude Code 生成(几秒钟完成):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
const express = require('express');
const { body, validationResult } = require('express-validator');

const app = express();
app.use(express.json());

// 用户数据存储
let users = [];

// 获取用户列表
app.get('/users', (req, res) => {
res.json({
success: true,
data: users,
count: users.length
});
});

// 创建用户
app.post('/users',
[
body('name').notEmpty().withMessage('姓名不能为空'),
body('email').isEmail().withMessage('邮箱格式不正确')
],
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}

const user = {
id: Date.now().toString(),
name: req.body.name,
email: req.body.email,
createdAt: new Date()
};

users.push(user);
res.status(201).json({
success: true,
data: user
});
}
);

// 更新用户
app.put('/users/:id',
[
body('name').optional().notEmpty(),
body('email').optional().isEmail()
],
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}

const userIndex = users.findIndex(u => u.id === req.params.id);
if (userIndex === -1) {
return res.status(404).json({
success: false,
message: '用户不存在'
});
}

users[userIndex] = {
...users[userIndex],
...req.body,
updatedAt: new Date()
};

res.json({
success: true,
data: users[userIndex]
});
}
);

// 删除用户
app.delete('/users/:id', (req, res) => {
const userIndex = users.findIndex(u => u.id === req.params.id);
if (userIndex === -1) {
return res.status(404).json({
success: false,
message: '用户不存在'
});
}

users.splice(userIndex, 1);
res.json({
success: true,
message: '删除成功'
});
});

app.listen(3000, () => {
console.log('Server running on port 3000');
});

案例 2:重构遗留代码

场景:有一段复杂的回调地狱代码

原始代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
getUser(userId, function(err, user) {
if (err) return handleError(err);

getOrders(user.id, function(err, orders) {
if (err) return handleError(err);

processOrders(orders, function(err, results) {
if (err) return handleError(err);

sendEmail(user.email, results, function(err) {
if (err) return handleError(err);
console.log('Done');
});
});
});
});

Prompt

1
将这段回调地狱代码重构为 Promise 和 async/await

Claude Code 重构

1
2
3
4
5
6
7
8
9
10
11
async function processUserOrders(userId) {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const results = await processOrders(orders);
await sendEmail(user.email, results);
console.log('Done');
} catch (err) {
handleError(err);
}
}

案例 3:自动生成测试

Prompt

1
2
为上面的用户 API 生成完整的单元测试
使用 Jest 框架

Claude Code 生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
const request = require('supertest');
const app = require('./app');

describe('Users API', () => {
beforeEach(() => {
// 重置用户数据
users = [];
});

describe('GET /users', () => {
it('should return empty array when no users', async () => {
const res = await request(app).get('/users');
expect(res.status).toBe(200);
expect(res.body.success).toBe(true);
expect(res.body.data).toEqual([]);
});

it('should return users list', async () => {
// 先创建一个用户
await request(app)
.post('/users')
.send({ name: 'Test', email: 'test@example.com' });

const res = await request(app).get('/users');
expect(res.body.count).toBe(1);
});
});

describe('POST /users', () => {
it('should create a new user', async () => {
const res = await request(app)
.post('/users')
.send({ name: 'John', email: 'john@example.com' });

expect(res.status).toBe(201);
expect(res.body.success).toBe(true);
expect(res.body.data.name).toBe('John');
});

it('should validate email format', async () => {
const res = await request(app)
.post('/users')
.send({ name: 'John', email: 'invalid-email' });

expect(res.status).toBe(400);
expect(res.body.success).toBe(false);
});

it('should require name', async () => {
const res = await request(app)
.post('/users')
.send({ email: 'john@example.com' });

expect(res.status).toBe(400);
});
});

describe('PUT /users/:id', () => {
it('should update existing user', async () => {
// 创建用户
const createRes = await request(app)
.post('/users')
.send({ name: 'John', email: 'john@example.com' });

const userId = createRes.body.data.id;

// 更新用户
const updateRes = await request(app)
.put(`/users/${userId}`)
.send({ name: 'John Updated' });

expect(updateRes.status).toBe(200);
expect(updateRes.body.data.name).toBe('John Updated');
});

it('should return 404 for non-existing user', async () => {
const res = await request(app)
.put('/users/non-existing-id')
.send({ name: 'Updated' });

expect(res.status).toBe(404);
});
});

describe('DELETE /users/:id', () => {
it('should delete existing user', async () => {
const createRes = await request(app)
.post('/users')
.send({ name: 'John', email: 'john@example.com' });

const userId = createRes.body.data.id;

const deleteRes = await request(app).delete(`/users/${userId}`);
expect(deleteRes.status).toBe(200);
expect(deleteRes.body.success).toBe(true);

// 验证已删除
const getRes = await request(app).get('/users');
expect(getRes.body.count).toBe(0);
});
});
});

最佳实践

1. 清晰的 Prompt

差的 Prompt

1
写个登录功能

好的 Prompt

1
2
3
4
5
6
实现用户登录功能,要求:
1. 使用 JWT 认证
2. 密码使用 bcrypt 加密
3. 包含参数验证(邮箱、密码)
4. 错误处理:用户不存在、密码错误、服务器错误
5. 返回用户信息和 token

2. 上下文理解

Claude Code 能理解项目结构:

1
2
查看 src/auth 目录,理解当前的认证逻辑,
然后添加 "记住我" 功能,保持 7 天登录状态

3. 渐进式开发

1
2
3
4
5
第一步:创建基本功能
第二步:添加参数验证
第三步:添加错误处理
第四步:编写单元测试
第五步:添加文档注释

4. 代码审查

1
2
3
4
审查 src/utils.js 文件:
1. 检查性能问题
2. 检查安全问题
3. 提出优化建议

高级技巧

1. 项目级重构

1
2
3
4
将整个项目从 JavaScript 迁移到 TypeScript
1. 先转换类型定义
2. 然后转换核心模块
3. 最后添加严格模式

2. 文档生成

1
2
为所有 API 接口生成 OpenAPI 文档
包含请求参数、响应格式、错误码

3. 性能优化

1
2
3
4
5
分析 src/api/users.js 的性能瓶颈
并提出优化方案,包括:
1. 数据库查询优化
2. 缓存策略
3. 并发处理

注意事项

⚠️ 安全考虑

  • 不要让 AI 处理敏感数据(密码、密钥等)
  • 审查生成的代码,特别是安全相关的部分
  • 使用环境变量存储配置

⚠️ 代码质量

  • AI 生成的代码需要人工审查
  • 运行测试确保功能正确
  • 检查是否符合团队规范

⚠️ Token 限制

  • 大文件可能超出 token 限制
  • 可以分模块处理
  • 使用 .claudeignore 排除不相关文件

与其他工具对比

工具 优势 劣势
Claude Code 理解整个项目、质量高 需要付费、CLI 操作
GitHub Copilot IDE 集成、实时建议 上下文有限、代码质量不稳定
Cursor 图形界面、易用 功能相对简单
ChatGPT 免费、功能全面 无法直接操作代码库

总结

Claude Code 不是一个替代开发者的工具,而是一个强大的协作伙伴。它能:

  • 提升开发效率 3-5 倍
  • 减少重复性工作
  • 帮助学习和理解新框架
  • 提供最佳实践建议

推荐阅读


下一步:尝试用 Claude Code 重构你当前项目的一个模块,体验 AI 辅助开发的魅力!