docs: add comprehensive documentation in Chinese

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
WJQSERVER 2026-02-18 14:28:03 +00:00
parent 0f7f68fb89
commit 8a5acb0b04
10 changed files with 714 additions and 6 deletions

77
docs/advanced.md Normal file
View file

@ -0,0 +1,77 @@
# 高级特性与优化
本章节涵盖了 Touka 的一些深层特性以及在生产环境中的最佳实践。
## 性能优化
### 1. Context 池化
Touka 使用 `sync.Pool` 来重用 `touka.Context` 对象。这极大减少了每个请求产生的内存分配和 GC 压力。
- **代价**: 您必须在处理器返回后立即停止对该 `Context` 指针的任何引用。
- **解决方案**: 如果需要在后台 Goroutine 中使用请求数据,请预先提取所需数据(如 `c.Query` 的值),或者深拷贝该对象(不推荐)。
### 2. 预分配参数切片
在路由匹配过程中Touka 会预分配路径参数切片,并根据路由深度进行缓存,从而在路由查找时实现几乎零分配。
## 优雅停机 (Graceful Shutdown)
在部署新版本时,我们希望服务器停止接收新请求,但能处理完当前正在进行的请求。
```go
r := touka.Default()
// ... 注册路由 ...
// 监听 SIGINT 和 SIGTERM 信号
// 如果在 10 秒内未处理完,则强制关闭
if err := r.RunShutdown(":8080", 10*time.Second); err != nil {
log.Fatal("服务器退出异常:", err)
}
```
## 与标准库集成
Touka 遵循 `net/http` 哲学。您可以方便地使用现有的标准库组件。
### 适配 `http.HandlerFunc`
```go
r.GET("/pprof/*any", touka.AdapterStdFunc(pprof.Index))
```
### 手动注入
由于 `Engine` 实现了 `http.Handler` 接口,您可以将其挂载到任何地方。
```go
s := &http.Server{
Addr: ":8080",
Handler: r, // Engine 实例
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
s.ListenAndServe()
```
## 自定义日志集成
Touka 默认集成了 `reco` 日志库。您可以自定义其输出行为。
```go
logConfig := reco.Config{
Level: reco.LevelInfo,
Output: os.Stdout,
Async: true, // 异步写入提高性能
}
r.SetLoggerCfg(logConfig)
```
## 内存读取限制 (MaxReader)
为了防止恶意的大数据包攻击(如慢速 HTTP 攻击或内存溢出Touka 内置了 `MaxReader` 机制。
```go
// 设置全局最大读取限制(例如 2MB
r.SetMaxReader(2 << 20)
```

120
docs/context.md Normal file
View file

@ -0,0 +1,120 @@
# 上下文 (Context)
`touka.Context` 是 Touka 框架中最重要的结构。它携带了关于当前 HTTP 请求的所有必要信息,并提供了一系列方法来解析请求和构建响应。
## 请求数据解析
### 查询参数 (Query Parameters)
```go
// /welcome?firstname=Jane&lastname=Doe
r.GET("/welcome", func(c *touka.Context) {
firstname := c.DefaultQuery("firstname", "Guest")
lastname := c.Query("lastname") // 快捷方式,不存在则为空
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
```
### 表单数据 (Form Data)
```go
r.POST("/form_post", func(c *touka.Context) {
message := c.PostForm("message")
nick := c.DefaultPostForm("nick", "anonymous")
c.JSON(http.StatusOK, touka.H{
"status": "posted",
"message": message,
"nick": nick,
})
})
```
### JSON 绑定
Touka 提供了非常便捷的 JSON 绑定功能,它会自动解析请求体并填充到结构体中,同时进行基本的验证。
```go
type LoginRequest struct {
User string `json:"user" binding:"required"`
Password string `json:"password" binding:"required"`
}
r.POST("/login", func(c *touka.Context) {
var json LoginRequest
if err := c.ShouldBindJSON(&json); err != nil {
c.JSON(http.StatusBadRequest, touka.H{"error": err.Error()})
return
}
if json.User != "admin" || json.Password != "123" {
c.JSON(http.StatusUnauthorized, touka.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, touka.H{"status": "you are logged in"})
})
```
## 响应构建
### 基础格式
Touka 支持多种响应格式:
```go
// JSON
c.JSON(http.StatusOK, touka.H{"message": "hey"})
// 字符串 (支持格式化)
c.String(http.StatusOK, "welcome %s", name)
// 纯文本
c.Text(http.StatusOK, "just text")
// HTML 模板
c.HTML(http.StatusOK, "index.tmpl", touka.H{"title": "Main website"})
```
### 文件与流
```go
// 服务本地文件
c.File("/local/file.go")
// 将文件内容作为响应体(不触发下载)
c.SetRespBodyFile(http.StatusOK, "config.json")
// 写入数据流
c.WriteStream(reader)
```
### 重定向
```go
c.Redirect(http.StatusMovedPermanently, "http://google.com/")
```
## 数据传递 (Keys/Values)
您可以在中间件和处理器之间共享数据。
```go
// 在中间件中设置
c.Set("user_id", 12345)
// 在处理器中获取
id, exists := c.Get("user_id")
val := c.MustGet("user_id").(int)
```
## 状态管理
- `c.Abort()`: 停止执行后续的处理器/中间件。
- `c.Next()`: 执行后续的处理链。这常用于中间件中,在执行完某些前置逻辑后,显式调用 `Next`,并在其返回后执行后置逻辑。
## 对象池化
为了提高性能Touka 的 Context 对象是复用的。
**重要提示:不要在 Goroutine 中持久化持有 `touka.Context` 指针。如果您需要在 Goroutine 中使用请求数据,请务必在派生 Goroutine 前提取所需的值。**

66
docs/error-handling.md Normal file
View file

@ -0,0 +1,66 @@
# 统一错误处理
Touka 的核心优势之一是其**高度统一且自动化**的错误处理机制。
## 全局错误处理器
您可以为整个引擎设置一个统一的错误处理器。无论错误来自您的业务代码,还是来自框架内部(如 404/405甚至是来自标准库的 `http.FileServer`,最终都会流向这个处理器。
```go
r.SetErrorHandler(func(c *touka.Context, code int, err error) {
// 您可以在这里定义统一的错误响应格式
c.JSON(code, touka.H{
"code": code,
"message": http.StatusText(code),
"detail": err.Error(),
})
// 也可以记录日志
c.Errorf("HTTP Error %d: %v", code, err)
})
```
## `errorCapturingResponseWriter` (ecw) 的工作原理
很多时候,我们希望拦截标准库组件(如 `http.FileServer`)产生的错误,以便能够应用我们自定义的 404 页面或 JSON 响应。
Touka 通过包装标准的 `http.ResponseWriter` 实现了这一点:
1. **拦截写入**: 当 `http.FileServer` 等组件尝试调用 `WriteHeader(statusCode)``statusCode >= 400`Touka 的包装器会捕获这个状态码。
2. **阻止输出**: 它会阻止组件继续向响应体写入默认的错误消息(如 `404 page not found`)。
3. **回调处理**: 包装器随后会调用全局配置的 `ErrorHandler`
这意味着您可以像这样轻松地为静态文件服务设置自定义错误处理:
```go
r := touka.New()
// 设置全局错误处理
r.SetErrorHandler(func(c *touka.Context, code int, err error) {
if code == http.StatusNotFound {
c.String(http.StatusNotFound, "找不到此资源")
return
}
c.String(code, "发生错误: %v", err)
})
// 服务静态目录
r.StaticDir("/static", "./public")
// 如果用户访问 /static/missing-file.jpg他将看到 "找不到此资源"
```
## 手动触发错误处理
您也可以在处理器中通过 `c.ErrorUseHandle` 手动触发此流程:
```go
r.GET("/item/:id", func(c *touka.Context) {
item, err := db.GetItem(c.Param("id"))
if err != nil {
// 调用全局错误处理器
c.ErrorUseHandle(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, item)
})
```

26
docs/introduction.md Normal file
View file

@ -0,0 +1,26 @@
# Touka (灯花) 简介
Touka 是一个基于 Go 语言构建的高性能、多层次 Web 框架。其设计目标是为开发者提供**更直接的控制、有效的扩展能力,以及针对特定场景的行为优化**。
## 为什么选择 Touka
在众多的 Go Web 框架中Touka 致力于在保持类似 Gin 的易用性的同时,提供更深度的底层控制和更强大的统一错误处理机制。
### 核心特性
- **高性能路由**: 基于基数树Radix Tree实现的路由系统支持高效的路径匹配、参数捕获和通配符路由。
- **极致性能优化**:
- **Context 复用**: 通过对象池sync.Pool管理 `touka.Context`,显著减少 GC 压力。
- **最小化内存分配**: 在热点路径上尽可能减少临时对象的产生。
- **统一错误处理**: 独创的 `errorCapturingResponseWriter` 机制,能够捕获包括标准库 `http.FileServer` 在内的所有组件产生的错误状态码,并交由全局处理器统一处理。
- **无缝集成 SSE**: 内置对 Server-Sent Events 的支持,提供简单易用的回调式 API 和高度灵活的通道式 API。
- **静态资源增强**: 针对本地文件、目录以及 Go 嵌入式文件系统embed.FS提供了开箱即用的支持。
- **标准库兼容**: 提供了适配器,可以轻松将现有的 `http.Handler``http.HandlerFunc` 集成到 Touka 中。
## 设计哲学
1. **直接性**: 框架 API 设计直观,尽可能减少开发者需要记忆的概念。
2. **可扩展性**: 每一个核心组件(如日志、错误处理器、渲染器)都是可插拔或可定制的。
3. **健壮性**: 内置优雅停机支持,确保在服务器更新或关闭时请求能得到正确处理。
Touka 不仅仅是一个处理 HTTP 请求的工具,它还是构建现代化、可维护、高可用 Web 应用的坚实基础。

99
docs/middleware.md Normal file
View file

@ -0,0 +1,99 @@
# 中间件 (Middleware)
中间件是运行在 HTTP 请求处理链中的函数。它们可以拦截请求、修改数据、控制流向(通过 `c.Next()``c.Abort()`),并执行通用的前置/后置逻辑。
## 如何使用中间件
### 全局中间件
全局中间件应用于所有注册的路由。
```go
r := touka.New()
r.Use(touka.Recovery()) // 崩溃恢复
r.Use(MyCustomLogger()) // 自定义日志
```
### 路由组中间件
仅应用于特定组下的路由。
```go
api := r.Group("/api")
api.Use(AuthMiddleware())
{
api.GET("/user", handleUser)
}
```
## 编写自定义中间件
中间件的函数签名是 `touka.HandlerFunc`
### 示例:请求计时器
```go
func TimerMiddleware() touka.HandlerFunc {
return func(c *touka.Context) {
// --- 前置逻辑 ---
start := time.Now()
// 执行处理链中的下一个函数
c.Next()
// --- 后置逻辑 ---
duration := time.Since(start)
log.Printf("Request %s %s took %v", c.Request.Method, c.Request.URL.Path, duration)
}
}
```
### 示例:简单的 API 密钥验证
```go
func APIKeyAuth() touka.HandlerFunc {
return func(c *touka.Context) {
apiKey := c.GetHeader("X-API-KEY")
if apiKey != "secret-token" {
// 验证失败,返回错误并中止后续逻辑
c.JSON(http.StatusUnauthorized, touka.H{"error": "Invalid API Key"})
c.Abort()
return
}
// 验证通过,继续执行
c.Next()
}
}
```
## 内置中间件
- **Recovery**: 捕获任何发生的 panic恢复运行并返回 500 错误。它还负责调用全局错误处理器。
Touka 的设计非常精简,许多扩展功能(如 Gzip, JWT, Sessions由外部或第三方库提供您可以轻松通过 `r.Use()` 集成它们。
## 条件中间件 (Conditional Middleware)
Touka 支持根据布尔条件动态启用或禁用中间件。这在根据环境配置启用插件时非常有用。
### `UseIf`
```go
// 仅在 Debug 模式下启用日志
r.Use(r.UseIf(config.IsDebug, MyDebugLogger))
```
### `UseChainIf` (条件中间件链)
如果您有一组相关的中间件需要根据同一条件启用,可以使用 `UseChainIf`
```go
r.Use(r.UseChainIf(config.EnableMetrics,
MetricsMiddleware,
PrometheusMiddleware,
MonitoringMiddleware,
))
```
这些方法利用了 `MiddlewareXFunc`(即返回 `HandlerFunc` 的工厂函数),确保中间件实例按需创建或高效复用。

76
docs/quickstart.md Normal file
View file

@ -0,0 +1,76 @@
# 快速开始
本指南将帮助您在几分钟内启动并运行一个 Touka 应用。
## 安装
确保您的环境中已经安装了 Go 1.25 或更高版本。
在您的项目目录中运行:
```bash
go get github.com/infinite-iroha/touka
```
## 基础示例
创建一个 `main.go` 文件,并粘贴以下代码:
```go
package main
import (
"net/http"
"time"
"log"
"github.com/infinite-iroha/touka"
)
func main() {
// 1. 创建默认引擎(包含 Recovery 中间件)
r := touka.Default()
// 2. 注册一个简单的 GET 路由
r.GET("/ping", func(c *touka.Context) {
c.JSON(http.StatusOK, touka.H{
"message": "pong",
"time": time.Now().Unix(),
})
})
// 3. 注册带参数的路由
r.GET("/hello/:name", func(c *touka.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello, %s!", name)
})
// 4. 启动服务器并监听 8080 端口
log.Println("Touka server is running on :8080")
if err := r.Run(":8080"); err != nil {
log.Fatalf("Server failed: %v", err)
}
}
```
## 运行应用
执行以下命令启动服务器:
```bash
go run main.go
```
现在,您可以访问:
- `http://localhost:8080/ping`
- `http://localhost:8080/hello/World`
## 优雅停机
在生产环境中,我们推荐使用 `RunShutdown` 方法来启动服务器,它会监听系统信号并在关闭前等待正在处理的请求完成。
```go
// 等待 10 秒以处理剩余请求
if err := r.RunShutdown(":8080", 10*time.Second); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
```

94
docs/routing.md Normal file
View file

@ -0,0 +1,94 @@
# 路由系统
Touka 拥有一个强大且灵活的路由系统底层基于高性能的基数树Radix Tree实现。
## 基础路由
您可以为所有标准的 HTTP 方法注册处理器:
```go
r.GET("/someGet", handle)
r.POST("/somePost", handle)
r.PUT("/somePut", handle)
r.DELETE("/someDelete", handle)
r.PATCH("/somePatch", handle)
r.HEAD("/someHead", handle)
r.OPTIONS("/someOptions", handle)
// 注册所有上述方法的路由
r.ANY("/any", handle)
```
## 路径参数 (Named Parameters)
使用冒号 `:` 定义路径参数。参数值可以通过 `c.Param(key)` 获取。
```go
// 匹配 /user/john, 不匹配 /user/ 或 /user/john/send
r.GET("/user/:name", func(c *touka.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
})
// 匹配 /user/john/send
r.GET("/user/:name/:action", func(c *touka.Context) {
name := c.Param("name")
action := c.Param("action")
c.String(http.StatusOK, "%s is doing %s", name, action)
})
```
## 通配符路由 (Catch-all Parameters)
使用星号 `*` 定义通配符路由,它会捕获路径中该位置之后的所有内容。
```go
// 匹配 /src/main.go, /src/scripts/app.js 等
r.GET("/src/*filepath", func(c *touka.Context) {
path := c.Param("filepath")
c.String(http.StatusOK, "Viewing file: %s", path)
})
```
## 路由组 (RouterGroup)
路由组允许您共享公共路径前缀或中间件,使代码结构更清晰。
```go
v1 := r.Group("/api/v1")
{
v1.GET("/login", loginEndpoint)
v1.GET("/submit", submitEndpoint)
}
v2 := r.Group("/api/v2")
v2.Use(AuthMiddleware()) // 仅应用于 v2 组
{
v2.POST("/data", dataEndpoint)
}
```
## 路由行为配置
Touka 允许您自定义路由匹配的行为:
- **RedirectTrailingSlash**: 如果启用(默认),请求 `/foo/` 会被重定向到 `/foo`(如果只有后者注册了),反之亦然。
- **RedirectFixedPath**: 如果启用(默认),引擎会尝试修复路径大小写或移除多余的斜杠并重定向。
- **HandleMethodNotAllowed**: 如果启用,当请求路径匹配但方法不匹配时,返回 405 而非 404。
```go
r := touka.New()
r.RedirectTrailingSlash = true
r.HandleMethodNotAllowed = true
```
## 获取已注册路由信息
您可以使用 `GetRouterInfo` 获取当前引擎中所有已注册路由的列表。
```go
routes := r.GetRouterInfo()
for _, route := range routes {
fmt.Printf("Method: %s, Path: %s\n", route.Method, route.Path)
}
```

79
docs/sse.md Normal file
View file

@ -0,0 +1,79 @@
# Server-Sent Events (SSE)
Server-Sent Events 允许服务器向客户端实时推送数据。Touka 对此提供了原生且易用的支持。
## 核心结构:`Event`
`Event` 结构体代表一个 SSE 消息:
```go
type Event struct {
Event string // 事件名称
Data string // 数据内容 (支持多行)
Id string // 事件 ID
Retry string // 重连时间 (毫秒)
}
```
## 模式一:回调模式 (EventStream)
这是最推荐的使用方式,它更简单且能自动管理连接生命周期。
```go
r.GET("/events", func(c *touka.Context) {
c.EventStream(func(w io.Writer) bool {
// 构建事件
event := touka.Event{
Data: "现在的时间是: " + time.Now().Format(time.RFC3339),
}
// 渲染并写入
if err := event.Render(w); err != nil {
return false // 发生写入错误(如客户端断开),返回 false 停止流
}
time.Sleep(2 * time.Second)
return true // 返回 true 继续下一次循环
})
})
```
## 模式二:通道模式 (EventStreamChan)
如果您需要更高级的并发控制(例如从多个异步源接收数据),可以使用通道模式。
```go
r.GET("/events-chan", func(c *touka.Context) {
eventChan, errChan := c.EventStreamChan()
// 监听错误/断开连接
go func() {
if err := <-errChan; err != nil {
log.Printf("SSE 错误: %v", err)
}
}()
// 发送数据
go func() {
defer close(eventChan) // 务必在结束时关闭
for i := 0; i < 10; i++ {
select {
case <-c.Request.Context().Done():
return
default:
eventChan <- touka.Event{
Data: fmt.Sprintf("消息 #%d", i),
}
time.Sleep(1 * time.Second)
}
}
}()
})
```
## 最佳实践
1. **资源回收**: 确保在 `EventStreamChan` 模式下正确监听 `c.Request.Context().Done()` 以避免 Goroutine 泄漏。
2. **数据格式**: SSE 协议要求数据为 UTF-8。Touka 的 `Render` 方法会自动处理多行数据并加上必要的 `data:` 前缀。
3. **超时管理**: SSE 连接通常是长连接,请确保您的反向代理(如 Nginx配置了足够大的写超时时间。

63
docs/static-files.md Normal file
View file

@ -0,0 +1,63 @@
# 静态文件与资源
Touka 提供了多种方式来服务静态文件,这些方法都集成了 Touka 的统一错误处理机制。
## 服务本地目录
`StaticDir` 方法将 URL 路径映射到本地文件系统目录。
```go
// 访问 /assets/js/main.js 将读取 ./static/js/main.js
r.StaticDir("/assets", "./static")
```
## 服务单个文件
`StaticFile` 用于将特定的 URL 映射到单个本地文件。
```go
r.StaticFile("/favicon.ico", "./resources/favicon.ico")
```
## 集成 Go 嵌入式资源 (embed.FS)
使用 Go 1.16+ 的 `embed` 特性,您可以将整个静态前端项目编译进二进制文件中。
```go
//go:embed dist/*
var content embed.FS
func main() {
r := touka.Default()
// 剥离 "dist" 前缀并包装为 http.FS
fsroot, _ := fs.Sub(content, "dist")
// 使用 StaticFS 提供服务
r.StaticFS("/static", http.FS(fsroot))
// 您也可以使用 StaticFS 服务根路径
// r.StaticFS("/", http.FS(fsroot))
r.Run(":8080")
}
```
## 未匹配路径作为文件服务 (UnMatchFS)
这是一个独特的功能:当没有任何 API 路由匹配时尝试从指定的文件系统中查找并返回文件。这非常适合用于单页应用SPA的部署。
```go
r := touka.New()
r.SetUnMatchFS(http.Dir("./frontend/dist"), true)
// API 路由
r.GET("/api/status", handleStatus)
// 如果请求 /index.html 且没有 /index.html 的路由,
// 则会从 ./frontend/dist/index.html 读取。
```
## 性能提示
对于高负载的静态资源分发,虽然 Touka 表现出色,但我们仍建议在生产环境中使用 Nginx 或 CDN 站在 Touka 前面来处理静态文件,让 Touka 专注于处理动态逻辑。