跳转到内容

从 v1.17 升级到 v1.18

令人兴奋的新功能 🎉

功能增强 🚀

破坏性变化 🛠

升级指南

随着 Golang v1.24 不再被维护,Goravel 支持的 Golang 版本已从 1.24 升级到 1.25。

goravel/example 项目从 v1.17 升级到 v1.18 的 PR 可作为升级参考:goravel/example#133

你可以将以下内容复制粘贴到你的 AI 编程助手中,以自动升级你的 Goravel 项目。

markdown
# Goravel v1.18 升级指南

**开始之前**:确保所有更改已提交或暂存,项目可正常编译(`go build ./...`),Go ≥ 1.25 已安装,Goravel 版本 ≥ 1.17.0。

## 步骤 1:更新 Go 模块依赖

**检测**:检查已安装哪些 facade 包:

```shell
rg -l 'goravel/(gin|fiber|redis|s3|oss|cos|minio)' go.sum 2>/dev/null
```

**操作**:运行框架升级命令,仅升级 `go.sum` 中检测到的 facade 包:

```shell
go get github.com/goravel/framework@latest

# 对于检测到的每个 facade,运行对应的 go get。
# 如果 goravel/gin 在 go.sum 中:   go get github.com/goravel/gin@latest
# 如果 goravel/fiber 在 go.sum 中: go get github.com/goravel/fiber@latest
# 如果 goravel/redis 在 go.sum 中: go get github.com/goravel/redis@latest
# 如果 goravel/s3 在 go.sum 中:    go get github.com/goravel/s3@latest
# 如果 goravel/oss 在 go.sum 中:   go get github.com/goravel/oss@latest
# 如果 goravel/cos 在 go.sum 中:   go get github.com/goravel/cos@latest
# 如果 goravel/minio 在 go.sum 中: go get github.com/goravel/minio@latest

go mod tidy
```

## 步骤 2:将 HTTP 中间件从 func 迁移到 interface

`contracts/http.Middleware``func(http.Context)` 变更为包含 `Handle(http.Context)``Signature() string` 方法的 interface。每个自定义中间件都必须更新。

### 2a. 检测所有自定义中间件函数

```shell
rg -n 'func\s+\w+\s*\(.*\)\s+http\.Middleware' --type go
rg -n 'func\s+\w+\s*\(.*\)\s+contractshttp\.Middleware' --type go
```

同时搜索匿名中间件(内联 `return func(ctx http.Context)`):

```shell
rg -n 'return\s+func\(ctx\s+http\.Context\)' --type go
```

### 2b. 转换每个中间件

对于每个检测到的中间件,保留工厂函数签名,将闭包替换为返回结构体的形式:

```diff
 func Auth() http.Middleware {
-    return func(ctx http.Context) {
-        ctx.Request().Next()
-    }
+    return &Auth{}
 }
 
+type Auth struct{}
+
+func (a *Auth) Handle(ctx http.Context) {
+    ctx.Request().Next()
+}
+
+func (a *Auth) Signature() string {
+    return "auth"
+}
```

如果原始工厂函数接受参数,将它们作为结构体字段传入:

```diff
 func Throttle(name string) http.Middleware {
-    return func(ctx http.Context) {
-        // throttle logic using name
-    }
+    return &Throttle{Name: name}
 }
+
+type Throttle struct{ Name string }
+
+func (t *Throttle) Handle(ctx http.Context) { /* throttle logic using t.Name */ }
+func (t *Throttle) Signature() string { return "throttle-" + t.Name }
```

调用点如 `middleware.Auth()` 保持不变——工厂函数仍然返回 `http.Middleware`,但现在返回的是结构体而非闭包。

### 2c. 验证

```shell
# 确保没有残留的 func 形式中间件
rg -n 'func.*http\.Middleware' --type go

# 构建必须成功
go build ./...
```

## 步骤 3:更新验证规则和过滤器映射

验证规则和过滤器现在使用 `map[string]any` 而非 `map[string]string`

### 3a. 检测受影响的文件

```shell
rg -n 'map\[string\]string.*Validate\(' --type go
rg -n 'map\[string\]string.*Make\(' --type go
rg -n 'Rules\(\).*map\[string\]string' --type go
rg -n 'Filters\(\).*map\[string\]string' --type go
rg -n 'Messages\(\).*map\[string\]string' --type go
rg -n 'Attributes\(\).*map\[string\]string' --type go
```

同时搜索可能已使用 `map[string]any` 或使用短变量声明的 `Validate``Make` 调用:

```shell
rg -n '\.Validate\(.*map\[string\]' --type go
rg -n 'validation\.Make\(.*map\[string\]' --type go
```

### 3b. 应用转换

`Validate``Make` 调用中的 `map[string]string` 替换为 `map[string]any`

```diff
-ctx.Request().Validate(map[string]string{
+ctx.Request().Validate(map[string]any{
     "title": "required|max:255",
 })
```

同时更新表单请求的方法签名——`Rules()``Messages()``Attributes()``Filters()`

```diff
-func (r *UserCreate) Rules(ctx http.Context) map[string]string {
-    return map[string]string{
+func (r *UserCreate) Rules(ctx http.Context) map[string]any {
+    return map[string]any{
         "name": "required",
     }
 }

-func (r *UserCreate) Messages(ctx http.Context) map[string]string {
-    return map[string]string{}
+func (r *UserCreate) Messages(ctx http.Context) map[string]any {
+    return map[string]any{}
 }

-func (r *UserCreate) Filters(ctx http.Context) map[string]string {
-    return map[string]string{
+func (r *UserCreate) Filters(ctx http.Context) map[string]any {
+    return map[string]any{
         "name": "trim",
     }
 }
```

如果 `regex``not_regex` 规则值包含 `|` 且后面还有更多规则,请切换为 `[]string`

```diff
 rules := map[string]string{
-    "slug": "required|regex:^(news|docs)-[a-z]+$|string",
+    "slug": []string{"required", "regex:^(news|docs)-[a-z]+$", "string"},
 }
```

### 3c. 验证

```shell
# 确保验证上下文中没有残留的 map[string]string
rg -n 'Validate\(.*map\[string\]string' --type go
rg -n 'Make\(.*map\[string\]string' --type go
rg -n 'Rules\(.*map\[string\]string' --type go
rg -n 'Messages\(.*map\[string\]string' --type go
rg -n 'Filters\(.*map\[string\]string' --type go

go build ./...
```

## 步骤 4:更新邮件模板内容字段

邮件内容字段现在更加明确:`HtmlView` 用于 HTML 模板,`TextView` 用于纯文本模板,`Html`/`Text` 用于原始正文内容。

### 4a. 检测受影响的文件

```shell
rg -n '\.View\s*:' --type go -g '!vendor/**' -g '!storage/**'
rg -n 'Content\(.*mail\.Content\{' --type go
rg -n '&mail\.Content\{' --type go
```

### 4b. 应用转换

对于每个 `mail.Content{}`

| 旧字段 | 新字段 | 条件 |
|-----------|-----------|-----------|
| `View:` | `HtmlView:` | 始终(原本用于 HTML 模板) |
| `Text:`(值以 `.html``.txt``.tmpl` 结尾或是文件路径) | `TextView:` | 当值原本是模板路径时 |
| `Text:`(值是原始字符串,非路径) | `Text:` | 保持不变,用于原始正文 |

**示例 — 基于模板的邮件**
```diff
 Content(mail.Content{
-    View: "welcome.html",
-    Text: "welcome.txt",
+    HtmlView: "welcome.html",
+    TextView: "welcome.txt",
     With: map[string]any{
         "Name": "Goravel",
     },
 })
```

**示例 — 原始正文邮件(无需更改)**
```go
Content(mail.Content{Text: "Hello Goravel"})
```

如果文件中已经使用了 `Html:` / `Text:`(v1.17+ 原始正文模式),则无需更改。

### 4c. 验证

```shell
# 确保邮件 Content 结构体中没有残留的 View:
rg -n '\.View\s*:.*\.html' --type go

go build ./...
```

## 步骤 5:将 gRPC 配置键 servers 重命名为 clients

### 5a. 检测受影响的文件

```shell
rg -n '"servers"' --type go -g '*grpc*'
rg -n 'grpc\.servers' --type go
```

### 5b. 应用转换

`config/grpc.go` 中,重命名配置键:

```diff
 config.Add("grpc", map[string]any{
     "host": config.Env("GRPC_HOST", ""),
-    "servers": map[string]any{
+    "clients": map[string]any{
         "user": map[string]any{
             "host":           config.Env("GRPC_USER_HOST", ""),
             ...
         },
     },
 })
```

在应用程序代码中,更新任何配置路径引用:

```diff
-facades.Config().Get("grpc.servers.user.host")
+facades.Config().Get("grpc.clients.user.host")
```

### 5c. 验证

```shell
# 确保 gRPC 配置中没有残留的 "servers" 键
rg -n '"servers"' --type go -g '*grpc*'
rg -n 'grpc\.servers' --type go

go build ./...
```

## 步骤 6:更新验证错误消息和自定义规则名称

新的原生验证引擎([goravel/framework#1397](https://github.com/goravel/framework/pull/1397))生成 Laravel 风格的错误消息,并以不同方式评估所有规则(包括通配符)。任何断言特定验证错误消息或 JSON 错误响应的测试都必须更新。

### 6a. 检测受影响的测试

```shell
rg -n 'Equal.*message.*required' --type go
rg -n 'Equal.*message.*regex' --type go
rg -n 'Equal.*message.*date' --type go
rg -n 'Required.*is required to not be empty' --type go
```

### 6b. 更新错误消息断言

将旧格式的错误消息断言替换为新的 Laravel 风格消息:

| 旧消息 | 新消息 |
|-------------|-------------|
| `"name is required to not be empty"` | `"The name field is required."` |
| `"code value does not pass the regex check"` | `"The code field format is invalid."` |
| `"date value should be a date string"` | `"The date field must be a valid date."` |

示例测试 diff:

```diff
-s.Equal("{\"message\":{\"name\":{\"required\":\"name is required to not be empty\"}}}", content)
+s.Equal("{\"message\":{\"name\":{\"required\":\"The name field is required.\"}}}", content)
```

> **注意**:通配符规则(如 `scores.*``tags.*`)现在即使没有提供输入也会一致地出现在错误输出中。如果你的测试检查完整的错误响应体,请为之添加断言。

### 6c. 重命名与内置规则冲突的自定义规则

新引擎包含内置规则(`required``email``exists``unique` 等),可能与自定义规则名称冲突。重命名任何 `Signature()` 与内置规则名称匹配的自定义规则:

```diff
 func (receiver *Exists) Signature() string {
-    return "exists"
+    return "exists_custom"
 }
```

### 6d. 验证

```shell
go build ./...
go test ./... 2>&1 || true
```

## 步骤 7:安装 goravel-development skill

从 goravel-lite 下载 Goravel AI 编码规范 skill,以便在升级过程中参考项目结构、facades、测试模式和 Artisan 命令:

```shell
mkdir -p .agents/skills/goravel-development
curl -fsSL -o .agents/skills/goravel-development/SKILL.md \
  https://raw.githubusercontent.com/goravel/goravel-lite/master/.agents/skills/goravel-development/SKILL.md
```

### 7a. 验证

```shell
ls -l .agents/skills/goravel-development/SKILL.md
```

## 步骤 8:最终验证

完成所有步骤后,运行完整的验证套件:

```shell
go build ./...
go vet ./...
go test ./... 2>&1 || true
```

如果测试失败,最常见的原因有:
- 测试中的中间件函数未迁移(步骤 2)。
- 测试中的验证调用使用了 `map[string]string`(步骤 3)。
- 测试中的邮件 Content 结构体使用了旧字段名(步骤 4)。
- 测试中的验证错误消息断言格式为旧格式(步骤 6)。
- 自定义规则签名与内置规则名称冲突(步骤 6)。

功能介绍

AI SDK

Goravel v1.18 引入了官方 AI SDK,通过统一 facade 支持对话、附件、媒体生成和语音工作流。使用 go run .artisan package:install AI 安装 facade,然后安装对应的 provider 包,如 goravel/openaigoravel/anthropicgoravel/gemini

初始版本包含:

  • 智能体对话,支持 make:agentmake:tool、可自定义 AI 生成路径、PromptStream、工具调用、提示词中间件、provider 故障转移。
  • 请求作用域的文档和图片附件,支持从字节、reader、路径、存储、URL、上传或 provider 管理的文件 ID 添加。
  • 图片和音频生成,带 Store / StoreAs 辅助方法,以及支持语言和说话人分离的转录功能。

goravel-lite 脚手架还内置了 AI Agent Development Skill,能教导 AI 编程助手(Cursor、Copilot、Codex 等)如何在 Goravel 项目中工作。该 skill 涵盖项目结构、facades、路由、ORM、测试规范和 Artisan 脚手架命令。当 AI 助手读取此 skill 后,已经知道如何引导 Goravel 项目、使用 package:install 添加 facades、使用 facade 模式、使用 testify suites 和 mock 辅助方法编写测试,以及使用 Artisan make:* 生成器。你可以通过创建 .agents/skills/goravel-development/CUSTOM.md 用项目特定规则扩展该 skill。此 skill 与 goravel-lite 同步版本,并在升级框架时更新。

最简单的提示示例如下:

go
conversation, err := facades.AI().Agent(&agents.SupportAgent{})
if err != nil {
    return err
}

response, err := conversation.Prompt("How do I create a controller?")
if err != nil {
    return err
}

fmt.Println(response.Text())

查看文档

Telemetry

Goravel v1.18 引入了一个基于 OpenTelemetry 构建的可观测模块,用于收集追踪、指标和日志,并导出到任何兼容 OTLP 的后端,如 Jaeger、Prometheus、Grafana 或 Datadog。使用 go run . artisan package:install Telemetry 安装 facade。

初始版本包含:

  • 从单个 config/telemetry.go 文件配置追踪、指标和日志,支持 OTLP、控制台和自定义导出器,以及采样和批处理控制。
  • 内置 HTTP 服务器、HTTP 客户端、gRPC、数据库和日志记录的自动埋点,支持跨服务的追踪上下文传播。
  • 应用程序停止时自动 flush 和 shutdown。

手动创建 span 的示例:

go
ctx, span := facades.Telemetry().Tracer("app").Start(ctx, "process-order")
defer span.End()

span.SetAttributes(telemetry.String("order.id", "1234"))

// 将 ctx 传递下去以创建子 span
processItems(ctx)

Tracer 的参数("app")是标识生成 span 的代码的 instrumentation scope 名称,记录为 otel.scope.name。按惯例,它应为包导入路径,或对于应用程序代码,使用应用程序名称。

查看文档

Laravel 风格集合

Goravel 现在在 support/collect 包中提供了流畅的急切和延迟集合工具。你可以从切片创建集合,链式调用过滤和映射操作,对结构体使用 Laravel 风格的 Where 比较,聚合值,以及使用 LazyCollection 处理大型数据集。

go
numbers := collect.New(1, 2, 3, 4, 5, 6)

evens := numbers.Filter(func(n int, _ int) bool {
    return n%2 == 0
})

evens.All() // []int{2, 4, 6}

查看文档

原生验证引擎与内置规则

Goravel 现在使用原生验证引擎取代了之前的外部验证依赖。新引擎支持点号表示法、通配符验证、验证数据获取、自定义过滤器、显式消息优先级,以及更广泛的 Laravel 风格内置规则集。

go
validator, err := facades.Validation().Make(ctx,
    map[string]any{
        "email": " User@Goravel.dev ",
        "scores": []int{1, 2},
    },
    map[string]any{
        "email": "required|email",
        "scores.*": "required|integer",
    },
    validation.Filters(map[string]any{
        "email": "trim|lower",
    }),
)

validated := validator.Validated()
scores := validated["scores"].([]int)

查看文档

验证支持基于翻译的错误消息和 lang:publish 命令

验证错误消息现在由框架的本地化系统驱动。默认消息以内嵌形式包含为 lang/en/validation.json,并使用 validation.* 翻译键(如 validation.requiredvalidation.min.string)。

使用新的 lang:publish Artisan 命令将验证语言文件发布到你的应用程序:

shell
go run . artisan lang:publish
go run . artisan lang:publish --force

发布后,编辑 lang/en/validation.json 以自定义默认错误消息。为其他区域设置创建额外的语言目录(如 lang/zh/validation.json)。自定义规则消息现在支持 :value:optionN 占位符。

查看文档

维护模式命令

Goravel 现在提供了 artisan downartisan up 命令用于应用程序维护模式。默认使用 file 维护驱动,因此 down 命令会将维护元数据写入存储,up 命令则会将其移除。

shell
go run . artisan down
go run . artisan up

down 命令支持通过选项自定义响应,如 --reason--status--redirect--render--secret--with-secret

shell
go run . artisan down --reason="Upgrading database" --status=503
go run . artisan down --redirect=/maintenance
go run . artisan down --render=errors/503
go run . artisan down --with-secret

维护设置从 config/app.go 中的 app.maintenance 读取。新 Route 安装会自动添加此配置。现有应用程序可以手动添加:

go
"maintenance": map[string]any{
    "driver": config.Env("APP_MAINTENANCE_DRIVER", "file"),
    "store":  config.Env("APP_MAINTENANCE_STORE", ""),
},

对于多服务器部署,设置 APP_MAINTENANCE_DRIVER=cache 将维护状态存储在共享缓存存储中。使用 APP_MAINTENANCE_STORE 选择命名缓存存储,如 Redis:

ini
APP_MAINTENANCE_DRIVER=cache
APP_MAINTENANCE_STORE=redis

查看文档

可通过配置选项禁用 Runners

Goravel v1.18 将内部 app.auto_run 标志替换为一个公开的、支持 glob 匹配的 app.disabled_runners 配置选项。它允许你在主进程中选择性跳过自动运行的 runners,同时保持其他服务运行——例如,在专用容器中运行调度器而不禁用 HTTP 服务器,或通过跳过 goravel:httpgoravel:queuegoravel:grpc runners 构建仅调度器的容器。

该值是一个 glob 模式切片,与每个 runner 的签名通过 Go 的 path.Match 进行匹配:

go
// config/app.go
"app": map[string]any{
    "env":   "production",
    "debug": false,
    "disabled_runners": []string{"goravel:schedule"},
},

框架 runner 签名为 goravel:httpgoravel:grpcgoravel:queuegoravel:schedulegoravel:telemetry。模式按顺序评估,第一个匹配的生效,因此你可以混合使用精确匹配和通配符匹配:

go
// Web 容器 — 仅跳过调度器
"disabled_runners": []string{"goravel:schedule"},

// 仅调度器容器 — 禁用 http、queue、grpc
"disabled_runners": []string{"goravel:http", "goravel:queue", "goravel:grpc"},

// 禁用所有框架 runner
"disabled_runners": []string{"goravel:*"},

// 紧急开关:主进程中不运行任何自动运行 runner
"disabled_runners": []string{"*"},

无效模式会记录警告,runner 保持运行状态,因此配置中的拼写错误永远不会导致启动崩溃。过滤逻辑集中在 foundation.configureRunners() 中运行,因此未来添加的任何新 runner 都会自动获得相同的行为。

查看文档

路由支持 WithoutMiddleware

Goravel v1.18 为路由和路由组引入了 WithoutMiddleware 方法,类似于 Laravel 的 withoutMiddleware()。这允许特定路由绕过本应由父组应用的某些中间件——对 webhook 端点、公共 API 或应跳过身份验证或速率限制的路由非常有用。

在中间件组内排除单个路由的中间件:

go
facades.Route().Middleware(middleware.Auth(), middleware.Throttle("global")).Group(func(router route.Router) {
  router.Get("/dashboard", dashboardController.Index)

  // 此路由排除 throttle 中间件
  router.Get("/api/webhook", webhookController.Handle).
    WithoutMiddleware(middleware.Throttle("global"))
})

排除组内所有路由的中间件:

go
facades.Route().Middleware(middleware.Auth()).
  WithoutMiddleware(middleware.Auth()).
  Group(func(router route.Router) {
    router.Get("/public", publicController.Index)
  })

注意:中间件排除使用 Signature() 方法来识别中间件。确保每个中间件返回唯一的签名,以便 WithoutMiddleware 正常工作。内置框架中间件已提供唯一签名。

查看文档

build 命令支持 go generate

build 命令现在支持 --generate 标志和 -g 别名。启用后,Goravel 在编译二进制文件之前运行 go generate ./...,从而在不改变默认行为的情况下轻松将生成的代码纳入构建工作流。

shell
go run . artisan build --generate
go run . artisan build -g

查看文档

Artisan 控制台支持表格输出

Goravel 现在提供 ctx.Table 方法用于渲染结构化命令输出。它接受表头、表行和可选的 console.TableOption 用于边框、样式、列、宽度和高度自定义。

go
func (receiver *ReportCommand) Handle(ctx console.Context) error {
    headers := []string{"ID", "Name", "Status"}
    rows := [][]string{
        {"1", "Goravel", "Active"},
        {"2", "Framework", "Ready"},
    }

    ctx.Table(headers, rows)

    return nil
}

查看文档

Artisan 控制台支持通过 Shutdownable 优雅关闭

控制台命令现在可以通过实现可选的 console.Shutdownable 接口来支持优雅关闭。框架通过 signal.NotifyContext 为每个命令注入信号通知,因此按下 Ctrl+C(或发送 SIGTERM)会取消传递给 Handle 的 context。如果命令还实现了 Shutdown(ctx Context) error,框架会在进程退出前使用一个新的 context 和 30 秒的预算调用它进行清理。Handle 运行在 goroutine 中,因此信号响应是即时的。

console.Context 现在嵌入了 context.Context,因此命令可以直接使用 <-ctx.Done(),将 ctx 传递给期望 context.Context 的函数,并直接调用 ctx.Deadline() / ctx.Err() / ctx.Value(key) 而无需访问器。schedule:run 命令是典型的示例——它实现了 Shutdown 以将清理转发给 schedule.Shutdown(ctx)

go
package commands

import (
  "errors"
  "net/http"

  "github.com/goravel/framework/contracts/console"
  "github.com/goravel/framework/contracts/console/command"
)

type Serve struct {
  server *http.Server
}

func (r *Serve) Signature() string   { return "serve" }
func (r *Serve) Description() string { return "Start the HTTP server" }
func (r *Serve) Extend() command.Extend {
  return command.Extend{Category: "server"}
}

func (r *Serve) Handle(ctx console.Context) error {
  if err := r.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
    return err
  }
  return nil
}

func (r *Serve) Shutdown(ctx console.Context) error {
  ctx.Info("received signal, shutting down gracefully...")
  return r.server.Shutdown(ctx)
}

未实现 Shutdownable 的命令保持原有行为——进程在收到信号后立即退出。

查看文档

Artisan 控制台支持命令过滤

ApplicationBuilder 现在提供 WithCommandsFilter 方法,用于限定注册哪些内置 Artisan 命令——适用于不需要 make:*package:*vendor:publish 的生产环境二进制文件。回调返回要保留的命令签名的正向列表,支持精确匹配和 glob(*)匹配:

go
func Boot() contractsfoundation.Application {
	return foundation.Setup().
		WithCommands(Commands).
		WithCommandsFilter(func() []string {
			if facades.Config().GetString("app.env") == "production" {
				return []string{
					"up", "down", "key:generate", "about",
					"schedule:*",
					"queue:*",
				}
			}
			return nil // 其他环境保留所有命令
		}).
		WithConfig(config.Boot).
		Create()
}

查看文档

gRPC 支持 TLS 和 mTLS 凭证

Goravel 现在支持在应用程序引导期间注册 gRPC 服务端和客户端传输凭证。使用 WithGrpcServerCredentials 为服务端启用 TLS 或 mTLS,使用 WithGrpcClientCredentials 注册客户端凭证组,这些组可以通过 grpc.clients.<name>.credentials 引用。

go
return foundation.Setup().
    WithGrpcClientCredentials(func() map[string]credentials.TransportCredentials {
        userCreds, err := credentials.NewClientTLSFromFile("storage/certs/ca.crt", "user.example.com")
        if err != nil {
            panic(err)
        }

        return map[string]credentials.TransportCredentials{
            "user": userCreds,
        }
    }).
    Create()

查看文档

Orm 查询暴露上下文

Orm 查询现在提供 Context() 访问器。接收 contractsorm.Query 的代码(如模型全局作用域)可以读取通过 facades.Orm().WithContext(ctx) 传递的上下文。

go
"tenant": func(query contractsorm.Query) contractsorm.Query {
    tenantID, _ := query.Context().Value("tenant_id").(uint)

    return query.Where("tenant_id", tenantID)
}

查看文档

通过 LoadViewsFrom 加载包视图

扩展包现在可以通过 facades.View().LoadViewsFrom(path) 注册自己的视图目录。应用程序的 resources/views 目录具有最高优先级,注册的包视图作为默认值。Exist 方法也会检查已注册的包视图路径。

go
// 在扩展包的 service_provider.go 中
func (r *ServiceProvider) Boot(app foundation.Application) {
    facades.View().LoadViewsFrom("/path/to/package/views")
}

查看文档

注意:如果要在 Fiber 驱动中使用此功能,请参阅 Fiber 驱动模板配置替换为 DefaultTemplate

包配置支持导入别名

包配置的 modify 辅助方法现在支持 "<alias> <import-path>" 格式的导入别名。这使得当包导入路径较长或包应以自定义名称引用时,生成的引导文件保持可读性。

go
serviceProvider := "&admin.ServiceProvider{}"
moduleImport := "admin " + setup.Paths().Module().Import()

setup.Install(
    modify.RegisterProvider(moduleImport, serviceProvider),
).Uninstall(
    modify.UnregisterProvider(moduleImport, serviceProvider),
).Execute()

查看文档

队列驱动通过 DriverWithReceive 支持批量消费

自定义队列驱动现在可以选择实现 DriverWithReceive 接口(goravel/framework#1435),以支持阻塞批量消息消费。当驱动实现此接口时,队列 worker 会自动使用 Receive 而非 Pop,从而减少 Kafka 或 RabbitMQ 等高吞吐量驱动的轮询开销。

go
import (
  "context"
  "github.com/goravel/framework/contracts/queue"
)

type KafkaDriver struct{}

// 基础 Driver 接口方法(必需)
func (d *KafkaDriver) Driver() string                        { return "kafka" }
func (d *KafkaDriver) Push(task queue.Task, queue string) error { return nil }
func (d *KafkaDriver) Pop(queue string) (queue.ReservedJob, error) { return nil, nil }

// 可选的 DriverWithReceive,用于批量消费
func (d *KafkaDriver) Receive(ctx context.Context, queue string, count int) ([]queue.ReservedJob, error) {
  // 阻塞批量接收最多 `count` 个作业,ctx 到期时返回
}

Receive 可用时,worker 运行阻塞批量循环,每次调用超时 5 秒,并在错误或空批次时使用指数退避(100ms–3.2s)。context.Context 在 worker 关闭时被取消,确保干净终止。现有驱动无需任何应用程序代码更改。

查看文档

Log WithContext 过滤框架上下文键

以前,facades.Log().WithContext(ctx) 可能会将框架内部上下文键(如 GoravelAuthJwtgoravel_http_client_name)写入日志输出。Goravel 现在默认排除这些键,你可以通过 logging.context.exclude 添加项目特定的上下文键。

查看文档

安装器支持 agent skill 管理命令

goravel CLI 现在支持 goravel skill:listgoravel skill:install,用于发现和安装 Goravel agent skill。这些命令专为希望安装 AI 编程助手 skill 的贡献者设计,帮助他们遵循 Goravel 开发规范,如测试、命名和 pull request 工作流。

Skill 来源于 goravel/agents 仓库,默认安装到 ~/.agents/skills。你可以按名称过滤,通过 --path 选择自定义目标目录,并通过 --force 覆盖已有 skill。

查看文档

Fiber 驱动上下文与 Fiber 实例生命周期解耦

当使用 Fiber 驱动的请求超时时,context.Context 接口方法(Done()Deadline()Err())会委托给底层的 fiber.Ctx,而 fiber.Ctx 在请求被 fasthttp 回收后变为无效。这导致后台 goroutine(如 database/sql)仍持有请求上下文引用时出现空指针 panic(goravel/goravel#951)。

修复(goravel/fiber#253)将用户上下文和值作为结构体字段存储在 goravel Context 结构体上,而不是委托给 fiber.Ctx,因此超时信号可以正确传播,后台 goroutine 可以在请求返回后安全地访问上下文。

无需任何应用程序代码更改。

Fiber 驱动模板配置替换为 DefaultTemplate

为了支持通过 LoadViewsFrom 加载包视图,Fiber 驱动(goravel/fiber#273)已将依赖从 gofiber/template/html/v3 替换为内置的 DefaultTemplate(),该模板自动从 resources/views 和任何注册的包视图加载视图。如果你的 config/http.go 中有使用 html.New(path.Resource("views"), ".tmpl") 的自定义 template 配置,并且你想使用通过 LoadViewsFrom 加载包视图功能,请将其更新为使用 goravelfiber.DefaultTemplate()

diff
 // config/http.go — fiber 驱动

import goravelfiber "github.com/goravel/fiber"

 "template": func() (fiber.Views, error) {
-    return html.New(path.Resource("views"), ".tmpl"), nil
+    return goravelfiber.DefaultTemplate(), nil
 },

如需自定义分隔符或 FuncMap,请使用 goravelfiber.NewTemplate(goravelfiber.RenderOptions{...})

go
import goravelfiber "github.com/goravel/fiber"

"template": func() (fiber.Views, error) {
    return goravelfiber.NewTemplate(goravelfiber.RenderOptions{
        Delims: &goravelfiber.Delims{Left: "{[", Right: "]}"},
    })
},

如果未定义 template 配置,将自动使用 DefaultTemplate()——无需任何代码更改。

缓存前缀支持空字符串

Redis 缓存驱动现在支持空的 prefix 配置。以前,即使前缀为空,也会在每个键前自动添加冒号(:)分隔符,导致直接访问 Redis 时键不一致。现在,如果 config/cache.go 中的 prefix 选项设为空字符串,将不应用任何前缀或分隔符:

go
// config/cache.go
"prefix": "",

使用此配置,缓存键如 users 将以 users 存储在 Redis 中,而非 :users

已有非空前缀的项目无需任何应用程序代码更改。

查看文档

HTTP 中间件从 func 改为 interface

contracts/http.Middleware 已从函数类型 func(Context) 改为 interface:

go
type Middleware interface {
    Handle(Context)
    Signature() string
}

此变更为每个中间件提供了通过 Signature() 方法的稳定身份标识,使 WithoutMiddleware 能够通过签名比较可靠地排除特定中间件。所有内置框架中间件(CorsTimeoutThrottleVerifyCsrfToken 等)已更新为返回实现此接口的结构体。

自定义中间件必须更新为实现新接口。迁移步骤请参阅升级指南

查看文档

验证引擎原生重写

验证引擎在 goravel/framework#1397 中进行了重写——用与 Laravel 验证 API 完全兼容的原生实现替换了外部 gookit/validate 依赖。此变更引入了以下影响:

  1. 错误消息格式变更,从旧格式消息(如 "name is required to not be empty")变为 Laravel 风格消息(如 "The name field is required.")。断言特定验证错误消息的测试必须更新。

  2. 通配符规则(*.)现在一致地报告错误。 旧引擎可能会根据输入跳过通配符字段错误;新引擎始终评估并报告它们。

  3. 表单请求方法签名变更——Rules()Messages()Attributes()Filters() 现在返回 map[string]any 而非 map[string]string

  4. 自定义验证规则名称可能与新的内置规则冲突。 重命名与内置规则共享名称的自定义规则(如 existsexists_custom)。

错误消息现在由本地化系统驱动。默认消息以内嵌形式包含为 lang/en/validation.json,并使用 validation.* 翻译键。使用 go run . artisan lang:publish 发布并自定义它们。

查看文档

邮件内容区分原始文本与模板视图

Goravel 现在将 mail.Content.Text 视为原始纯文本邮件正文内容。模板路径更加明确:使用 HtmlView 表示 HTML 模板,TextView 表示纯文本模板。

当消息正文已就绪时,使用 HtmlText

go
func (m *WelcomeMail) Content() *mail.Content {
    return &mail.Content{
        Html: "<h1>Hello Goravel</h1>",
        Text: "Hello Goravel",
    }
}

当消息正文应从模板渲染时,使用 HtmlViewTextView

go
func (m *WelcomeMail) Content() *mail.Content {
    return &mail.Content{
        HtmlView: "welcome.html",
        TextView: "welcome.txt",
        With: map[string]any{
            "Name": "Goravel",
        },
    }
}

查看文档

gRPC 配置键从 servers 重命名为 clients

config/grpc.go 中的 gRPC 配置键 servers 已重命名为 clients,以准确反映这些条目注册的是 gRPC 客户端(消费远程服务)而非服务端。

config/grpc.go 中,重命名顶级键:

diff
 config.Add("grpc", map[string]any{
     "host": config.Env("GRPC_HOST", ""),
-    "servers": map[string]any{
+    "clients": map[string]any{
         "user": map[string]any{
             "host": config.Env("GRPC_USER_HOST", ""),
             ...
         },
     },
 })

在应用程序代码中,将任何配置路径引用从 grpc.servers 更新为 grpc.clients

diff
-facades.Config().Get("grpc.servers.user.host")
+facades.Config().Get("grpc.clients.user.host")

查看文档

HTTP 驱动超时中间件重写以保证并发安全

Gin 驱动(goravel/gin#222)和 Fiber 驱动(goravel/fiber#263)中的超时中间件已被重写,以修复一个并发 bug(goravel#966)。之前的实现在后台 goroutine 中运行处理器链,可能在处理器仍在已可被重用的请求作用域状态上运行时返回超时响应,导致高并发下出现跨请求响应污染。

两种驱动现在都委托给各自框架的原生超时中间件(Gin 使用 gin-contrib/timeout,Fiber 使用 Fiber 内置超时),这些中间件会正确放弃超时的请求上下文而非回收它们。Timeout(...) 中间件签名保持不变,ctx.Done() 仍然反映请求截止时间——无需任何应用程序代码更改。

查看文档

基于 MIT 许可发布