diff --git a/.vuepress/config/sidebar/en.ts b/.vuepress/config/sidebar/en.ts
index 2eecd65..3c2e0aa 100644
--- a/.vuepress/config/sidebar/en.ts
+++ b/.vuepress/config/sidebar/en.ts
@@ -105,14 +105,6 @@ export function getEnSidebar(): SidebarConfigArray {
text: "Artisan Console",
link: "/digging-deeper/artisan-console",
},
- {
- text: "Authentication",
- link: "/digging-deeper/authentication",
- },
- {
- text: "Authorization",
- link: "/digging-deeper/authorization",
- },
{
text: "Cache",
link: "/digging-deeper/cache",
@@ -143,6 +135,28 @@ export function getEnSidebar(): SidebarConfigArray {
},
],
},
+ {
+ text: "Security",
+ // collapsible: true,
+ children: [
+ {
+ text: "Authentication",
+ link: "/security/authentication",
+ },
+ {
+ text: "Authorization",
+ link: "/security/authorization",
+ },
+ {
+ text: "Encryption",
+ link: "/security/encryption",
+ },
+ {
+ text: "Hashing",
+ link: "/security/hashing",
+ },
+ ],
+ },
{
text: "ORM",
// collapsible: true,
diff --git a/.vuepress/config/sidebar/zh.ts b/.vuepress/config/sidebar/zh.ts
index efeed00..f9277cf 100644
--- a/.vuepress/config/sidebar/zh.ts
+++ b/.vuepress/config/sidebar/zh.ts
@@ -105,14 +105,6 @@ export function getZhSidebar(): SidebarConfigArray {
text: "Artisan 命令行",
link: "/zh/digging-deeper/artisan-console",
},
- {
- text: "用户验证",
- link: "/zh/digging-deeper/authentication",
- },
- {
- text: "用户授权",
- link: "/zh/digging-deeper/authorization",
- },
{
text: "缓存系统",
link: "/zh/digging-deeper/cache",
@@ -143,6 +135,28 @@ export function getZhSidebar(): SidebarConfigArray {
},
],
},
+ {
+ text: "安全相关",
+ // collapsible: true,
+ children: [
+ {
+ text: "用户验证",
+ link: "/zh/security/authentication",
+ },
+ {
+ text: "用户授权",
+ link: "/zh/security/authorization",
+ },
+ {
+ text: "加密解密",
+ link: "/zh/security/encryption",
+ },
+ {
+ text: "哈希",
+ link: "/zh/security/hashing",
+ },
+ ],
+ },
{
text: "ORM",
// collapsible: true,
diff --git a/ORM/getting-started.md b/ORM/getting-started.md
index 085bbe9..06879f9 100644
--- a/ORM/getting-started.md
+++ b/ORM/getting-started.md
@@ -77,6 +77,27 @@ For example, the model name is `UserOrder`, the table name is `user_orders`.
go run . artisan make:model User
```
+### Specify Table Name
+
+```go
+package models
+
+import (
+ "github.com/goravel/framework/database/orm"
+)
+
+type User struct {
+ orm.Model
+ Name string
+ Avatar string
+ orm.SoftDeletes
+}
+
+func (r *User) TableName() string {
+ return "goravel_user"
+}
+```
+
## facades.Orm available functions
| Name | Action |
@@ -99,11 +120,14 @@ go run . artisan make:model User
| Distinct | [Filter Repetition](#Filter-Repetition) |
| Driver | [Get Driver](#Get-Driver) |
| Exec | [Execute native update SQL](#Execute-Native-Update-SQL) |
-| Find | [Query one or multiple lines by ID](#Select) |
-| First | [Get one line](#Select) |
-| FirstOrCreate | [Query or create](#Select) |
+| Find | [Query one or multiple lines by ID](#Query-one-or-multiple-lines-by-ID) |
+| First | [Query one line](#Query-one-line) |
+| FirstOr | [Query or return data through callback](#Query-one-line) |
+| FirstOrCreate | [Retrieving Or Creating Models](#Retrieving-Or-Creating-Models) |
+| FirstOrNew | [Retrieving Or New Models](#Retrieving-Or-Creating-Models) |
+| FirstOrFail | [Not Found Error](#Not-Found-Error) |
| ForceDelete | [Force delete](#Delete) |
-| Get | [Query multiple lines](#Select) |
+| Get | [Query multiple lines](#Query-multiple-lines) |
| Group | [Group](#Group-By-&-Having) |
| Having | [Having](#Group-By-&-Having) |
| Join | [Join](#Join) |
@@ -121,8 +145,9 @@ go run . artisan make:model User
| Scopes | [Scopes](#Execute-Native-SQL) |
| Select | [Specify Fields](#Specify-Fields) |
| Table | [Specify a table](#Specify-Table-Query) |
-| Update | [Update a single column](#Save-Model) |
-| Updates | [Update multiple columns](#Save-Model) |
+| Update | [Update a single column](#Update-a-single-column) |
+| Updates | [Update multiple columns](#Update-multiple-columns) |
+| UpdateOrCreate | [Update or create](#Update-or-create) |
| Where | [Where](#Where) |
| WithTrashed | [Query soft delete data](#Query-Soft-Delete-Data) |
@@ -181,7 +206,7 @@ facades.Orm.WithContext(ctx).Query()
### Select
-Query one line
+#### Query one line
```go
var user models.User
@@ -189,7 +214,17 @@ facades.Orm.Query().First(&user)
// SELECT * FROM users WHERE id = 10;
```
-Query one or multiple lines by ID
+Sometimes you may wish to perform some other action if no results are found. The findOr and firstOr methods will return a single model instance or, if no results are found, execute the given closure. You can set values to model in closure:
+
+```go
+facades.Orm.Query().Where("name", "first_user").FirstOr(&user, func() error {
+ user.Name = "goravel"
+
+ return nil
+})
+```
+
+#### Query one or multiple lines by ID
```go
var user models.User
@@ -201,7 +236,7 @@ facades.Orm.Query().Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);
```
-When the primary key of the user table is `string` type, you need to specify the primary key when calling `Find` method
+#### When the primary key of the user table is `string` type, you need to specify the primary key when calling `Find` method
```go
var user models.User
@@ -209,7 +244,7 @@ facades.Orm.Query().Find(&user, "uuid=?" ,"a")
// SELECT * FROM users WHERE uuid = "a";
```
-Query multiple lines
+#### Query multiple lines
```go
var users []models.User
@@ -217,17 +252,38 @@ facades.Orm.Query().Where("id in ?", []int{1,2,3}).Get(&users)
// SELECT * FROM users WHERE id IN (1,2,3);
```
-Query or create
+#### Retrieving Or Creating Models
+
+The `FirstOrCreate` method will attempt to locate a database record using the given column / value pairs. If the model can not be found in the database, a record will be inserted with the attributes resulting from merging the first argument with the optional second argument:
+
+The `FirstOrNew` method, like `FirstOrCreate`, will attempt to locate a record in the database matching the given attributes. However, if a model is not found, a new model instance will be returned. Note that the model returned by `FirstOrNew` has not yet been persisted to the database. You will need to manually call the `Save` method to persist it:
```go
var user models.User
-facades.Orm.Query().Where("sex = ?", 1).FirstOrCreate(&user, models.User{Name: "tom"})
+facades.Orm.Query().Where("sex", 1).FirstOrCreate(&user, models.User{Name: "tom"})
// SELECT * FROM users where name="tom" and sex=1;
// INSERT INTO users (name) VALUES ("tom");
-facades.Orm.Query().Where("sex = ?", 1).FirstOrCreate(&user, models.User{Name: "tom"}, , models.User{Avatar: "avatar"})
+facades.Orm.Query().Where("sex", 1).FirstOrCreate(&user, models.User{Name: "tom"}, models.User{Avatar: "avatar"})
// SELECT * FROM users where name="tom" and sex=1;
// INSERT INTO users (name,avatar) VALUES ("tom", "avatar");
+
+var user models.User
+facades.Orm.Query().Where("sex", 1).FirstOrNew(&user, models.User{Name: "tom"})
+// SELECT * FROM users where name="tom" and sex=1;
+
+facades.Orm.Query().Where("sex", 1).FirstOrNew(&user, models.User{Name: "tom"}, models.User{Avatar: "avatar"})
+// SELECT * FROM users where name="tom" and sex=1;
+```
+
+#### Not Found Error
+
+When not fount model, `First` doesn't return error, if you want return an error, you can use `FirstOrFail`:
+
+```go
+var user models.User
+err := facades.Orm.Query().FirstOrFail(&user)
+// err == orm.ErrRecordNotFound
```
### Where
@@ -367,7 +423,7 @@ result := facades.Orm.Query().Create(&users)
### Save Model
-Update a existing model
+#### Update a existing model
```go
var user models.User
@@ -379,22 +435,32 @@ facades.Orm.Query().Save(&user)
// UPDATE users SET name='tom', age=100, updated_at = '2022-09-28 16:28:22' WHERE id=1;
```
-Update a single column
+#### Update a single column
```go
-facades.Orm.Query().Model(&models.User{}).Where("name = ?", "tom").Update("name", "hello")
+facades.Orm.Query().Model(&models.User{}).Where("name", "tom").Update("name", "hello")
// UPDATE users SET name='tom', updated_at='2022-09-28 16:29:39' WHERE name="tom";
```
-Update multiple columns
+#### Update multiple columns
```go
-facades.Orm.Query().Model(&user).Where("name = ?", "tom").Updates(User{Name: "hello", Age: 18})
+facades.Orm.Query().Model(&user).Where("name", "tom").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name="hello", age=18, updated_at = '2022-09-28 16:30:12' WHERE name = "tom";
```
-> When updating with `struct`, Orm will only update non-zero fields. You might want to use `map` to update attributes or use `Select` to specify fields to update.
+> When updating with `struct`, Orm will only update non-zero fields. You might want to use `map` to update attributes or use `Select` to specify fields to update. Note that `struct` can only be `Model`, if you want to update with non `Model`, you need to use `.Table("users")`, however, the `updated_at` field cannot be updated automatically at this time.
+#### Update or create
+
+Query by `name`, if not exist, create by `name`, `avatar`, if exists, update `avatar` based on `name`:
+
+```go
+facades.Orm.Query().UpdateOrCreate(&user, User{Name: "name"}, User{Avatar: "avatar"})
+// SELECT * FROM `users` WHERE `users`.`name` = 'name' AND `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1
+// INSERT INTO `users` (`created_at`,`updated_at`,`deleted_at`,`name`,`avatar`) VALUES ('2023-03-11 10:11:08.869','2023-03-11 10:11:08.869',NULL,'name','avatar')
+// UPDATE `users` SET `avatar`='avatar',`updated_at`='2023-03-11 10:11:08.881' WHERE `name` = 'name' AND `users`.`deleted_at` IS NULL AND `id` = 1
+```
### Delete
Delete by model
diff --git a/README.md b/README.md
index 3983587..5f25ff2 100644
--- a/README.md
+++ b/README.md
@@ -29,17 +29,12 @@ Welcome star, PR and issues!
- [x] Mail
- [x] Validation
- [x] Mock
+- [x] Hash
+- [x] Crypt
## Roadmap
-- [ ] Hash
-- [ ] Crypt
-- [ ] Support Websocket
-- [ ] Broadcasting
-- [ ] Delay Queue
-- [ ] Queue supports DB driver
-- [ ] Notifications
-- [ ] Optimize unit tests
+[For Detail](https://github.com/goravel/goravel/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
## Documentation
@@ -49,6 +44,16 @@ Example [https://github.com/goravel/example](https://github.com/goravel/example)
> To optimize the documentation, please submit a PR to the documentation repository [https://github.com/goravel/docs](https://github.com/goravel/docs)
+## Contributors
+
+This project exists thanks to all the people who contribute.
+
+
+
+
+
+
+
## Group
Welcome more discussion in Telegram.
diff --git a/architecutre-concepts/facades.md b/architecutre-concepts/facades.md
index 56fcd1e..0f52329 100644
--- a/architecutre-concepts/facades.md
+++ b/architecutre-concepts/facades.md
@@ -39,13 +39,15 @@ func (database *ServiceProvider) Boot() {
| Facade | Document |
| -------- | ------------------------------------------------------- |
| Artisan | [Command Console](../digging-deeper/artisan-console.md) |
-| Auth | [Authentication](../digging-deeper/authentication.md) |
-| Gate | [Authorization](../digging-deeper/authorization.md) |
+| Auth | [Authentication](../security/authentication.md) |
+| Gate | [Authorization](../security/authorization.md) |
| Cache | [Cache](../digging-deeper/cache.md) |
| Config | [Configuration](../getting-started/configuration.md) |
+| Crypt | [Encryption](../security/encryption.md) |
| Orm | [ORM](../orm/getting-started.md) |
| Event | [Event](../digging-deeper/event.md) |
| Grpc | [Grpc](../the-basics/grpc.md) |
+| Hash | [Hashing](../security/hashing.md) |
| Log | [Log](../the-basics/logging.md) |
| Queue | [Queue](../digging-deeper/queues.md) |
| Route | [Route](../the-basics/routing.md) |
diff --git a/digging-deeper/filesystem.md b/digging-deeper/filesystem.md
index 6154abf..0f41f6c 100644
--- a/digging-deeper/filesystem.md
+++ b/digging-deeper/filesystem.md
@@ -260,21 +260,27 @@ You need to implement the `github.com/goravel/framework/contracts/filesystem/Dri
```go
type Driver interface {
- WithContext(ctx context.Context) Driver
+ AllDirectories(path string) ([]string, error)
+ AllFiles(path string) ([]string, error)
+ Copy(oldFile, newFile string) error
+ Delete(file ...string) error
+ DeleteDirectory(directory string) error
+ Directories(path string) ([]string, error)
+ Exists(file string) bool
+ Files(path string) ([]string, error)
+ Get(file string) (string, error)
+ MakeDirectory(directory string) error
+ Missing(file string) bool
+ Move(oldFile, newFile string) error
+ Path(file string) string
Put(file, content string) error
PutFile(path string, source File) (string, error)
PutFileAs(path string, source File, name string) (string, error)
- Get(file string) (string, error)
Size(file string) (int64, error)
- Path(file string) string
- Exists(file string) bool
- Missing(file string) bool
- Url(file string) string
TemporaryUrl(file string, time time.Time) (string, error)
- Copy(oldFile, newFile string) error
- Move(oldFile, newFile string) error
- Delete(file ...string) error
- MakeDirectory(directory string) error
- DeleteDirectory(directory string) error
+ WithContext(ctx context.Context) Driver
+ Url(file string) string
}
```
+
+> Note: Since the configuration has not been loaded when the custom driver is registered, so please use `facades.Config.Env` to obtain the configuration in the custom driver.
\ No newline at end of file
diff --git a/digging-deeper/queues.md b/digging-deeper/queues.md
index f446d39..8a45da4 100644
--- a/digging-deeper/queues.md
+++ b/digging-deeper/queues.md
@@ -192,6 +192,14 @@ err := facades.Queue.Chain([]queue.Jobs{
}).Dispatch()
```
+### Delayed Dispatching
+
+If you would like to specify that a job should not be immediately available for processing by a queue worker, you may use the `Delay` method when dispatching the job. For example, let's specify that a job should not be available for processing until 10 minutes after it has been dispatched:
+
+```go
+err := facades.Queue.Job(&jobs.Test{}, []queue.Arg{}).Delay(time.Now().Add(100*time.Second)).Dispatch()
+```
+
### Customizing The Queue & Connection
#### Dispatching To A Particular Queue
diff --git a/getting-started/compile.md b/getting-started/compile.md
index 37d9101..378918e 100644
--- a/getting-started/compile.md
+++ b/getting-started/compile.md
@@ -33,8 +33,23 @@ CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build .
## Docker
-Goravel has a default Dockerfile file, you can use it directly.
+Goravel has a default `Dockerfile` and `docker-compose.yml` file, you can use it directly, note that `APP_HOST` should be `0.0.0.0` at this time.
```
docker build .
```
+
+### Docker Compose
+
+You can also quickly start the service with the following command:
+
+```
+docker-compose build
+docker-compose up
+```
+
+> Note: If you need external access, you need to change APP_HOST to 0.0.0.0
+
+## Reduce package size
+
+Commenting out the unused `ServiceProvider` in `ServiceProviders` will effectively reduce the packaging volume.
diff --git a/getting-started/installation.md b/getting-started/installation.md
index cda268b..7b2c77f 100644
--- a/getting-started/installation.md
+++ b/getting-started/installation.md
@@ -36,7 +36,7 @@ go run . --env=../.env
### Live reload
-Built-in [cosmtrek/air](https://github.com/cosmtrek/air) configuration file which can be used directly:
+Install [cosmtrek/air](https://github.com/cosmtrek/air), Goravel has a built-in configuration file that can be used directly:
```
air
@@ -66,7 +66,7 @@ go run. artisan key:generate
### Generate JWT Token
-You need to generate JWT Token if you use [Authentication](../digging-deeper/authentication.md).
+You need to generate JWT Token if you use [Authentication](../security/authentication.md).
```
go run . artisan jwt:secret
diff --git a/digging-deeper/authentication.md b/security/authentication.md
similarity index 88%
rename from digging-deeper/authentication.md
rename to security/authentication.md
index 50a9fea..60426c7 100644
--- a/digging-deeper/authentication.md
+++ b/security/authentication.md
@@ -45,9 +45,18 @@ token, err := facades.Auth.LoginUsingID(ctx, 1)
## Parse Token
```go
-err := facades.Auth.Parse(ctx, token)
+payload, err := facades.Auth.Parse(ctx, token)
```
+Through `payload` you can get:
+
+1. `Guard`: Current Guard;
+2. `Key`: User flag;
+3. `ExpireAt`: Expire time;
+4. `IssuedAt`: Issued time;
+
+> When `err` isn't nil other than `ErrorTokenExpired`, payload == nil
+
You can judge whether the Token is expired by err:
```go
diff --git a/digging-deeper/authorization.md b/security/authorization.md
similarity index 96%
rename from digging-deeper/authorization.md
rename to security/authorization.md
index db55c00..fedb6da 100644
--- a/digging-deeper/authorization.md
+++ b/security/authorization.md
@@ -35,7 +35,7 @@ func (receiver *AuthServiceProvider) Register() {
}
func (receiver *AuthServiceProvider) Boot() {
- facades.Gate.Define("update-post", func(ctx context.Context, arguments map[string]any) *access.Response {
+ facades.Gate.Define("update-post", func(ctx context.Context, arguments map[string]any) access.Response {
user := ctx.Value("user").(models.User)
post := arguments["post"].(models.Post)
@@ -106,7 +106,7 @@ if (response.Allowed()) {
Sometimes, you may wish to grant all abilities to a specific user. You may use the `Before` method to define a closure that is run before all other authorization checks:
```go
-facades.Gate.Before(func(ctx context.Context, ability string, arguments map[string]any) *access.Response {
+facades.Gate.Before(func(ctx context.Context, ability string, arguments map[string]any) access.Response {
user := ctx.Value("user").(models.User)
if isAdministrator(user) {
return access.NewAllowResponse()
@@ -121,7 +121,7 @@ If the before closure returns a non-nil result that result will be considered th
You may use the `After` method to define a closure to be executed after all other authorization checks:
```go
-facades.Gate.After(func(ctx context.Context, ability string, arguments map[string]any, result *access.Response) *access.Response {
+facades.Gate.After(func(ctx context.Context, ability string, arguments map[string]any, result access.Response) access.Response {
user := ctx.Value("user").(models.User)
if isAdministrator(user) {
return access.NewAllowResponse()
@@ -173,7 +173,7 @@ func NewPostPolicy() *PostPolicy {
return &PostPolicy{}
}
-func (r *PostPolicy) Update(ctx context.Context, arguments map[string]any) *access.Response {
+func (r *PostPolicy) Update(ctx context.Context, arguments map[string]any) access.Response {
user := ctx.Value("user").(models.User)
post := arguments["post"].(models.Post)
diff --git a/security/encryption.md b/security/encryption.md
new file mode 100644
index 0000000..d9a2097
--- /dev/null
+++ b/security/encryption.md
@@ -0,0 +1,29 @@
+# Encryption
+
+[[toc]]
+
+## Introduction
+
+Goravel's encryption services provide a simple, convenient interface for encrypting and decrypting text via OpenSSL using AES-256 encryption. All of Goravel's encrypted values are signed using a message authentication code (MAC) so that their underlying value can not be modified or tampered with once encrypted.
+
+## Configuration
+
+Before using Goravel's encrypter, you must set the `key` configuration option in your `config/app.go` configuration file. This configuration value is driven by the `APP_KEY` environment variable. You should use the `go run . artisan key:generate` command to generate this variable's value since the `key:generate` command will use Golang's secure random bytes generator to build a cryptographically secure key for your application.
+
+## Using The Encrypter
+
+### Encrypting A Value
+
+You may encrypt a value using the `EncryptString` method provided by the `facades.Crypt`. All encrypted values are encrypted using OpenSSL and the AES-256-CBC cipher. Furthermore, all encrypted values are signed with a message authentication code (MAC). The integrated message authentication code will prevent the decryption of any values that have been tampered with by malicious users:
+
+```go
+secret, err := facades.Crypt.EncryptString("goravel")
+```
+
+### Decrypting A Value
+
+You may decrypt values using the `DecryptString` method provided by the `facades.Crypt`. If the value can not be properly decrypted, such as when the message authentication code is invalid, an error will be return:
+
+```go
+str, err := facades.Crypt.DecryptString(secret)
+```
diff --git a/security/hashing.md b/security/hashing.md
new file mode 100644
index 0000000..cf90267
--- /dev/null
+++ b/security/hashing.md
@@ -0,0 +1,41 @@
+# Hashing
+
+[[toc]]
+
+## Introduction
+
+The Goravel `facades.Hash` provides secure Argon2id and Bcrypt hashing for storing user passwords. If you are using one of the Goravel application starter kits, Argon2id will be used for registration and authentication by default.
+
+## Configuration
+
+The default hashing driver for your application is configured in your application's `config/hashing.go` configuration file. There are currently several supported drivers: Argon2id and Bcrypt.
+
+## Basic Usage
+
+### Hashing Passwords
+
+You may hash a password by calling the `Make` method on the `facades.Hash`:
+
+```go
+password, err := facades.Hash.Make(password)
+```
+
+### Verifying That A Password Matches A Hash
+
+The `Check` method provided by the Hash facade allows you to verify that a given plain-text string corresponds to a given hash:
+
+```go
+if facades.Hash.Check('plain-text', hashedPassword) {
+ // The passwords match...
+}
+```
+
+### Determining If A Password Needs To Be Rehashed
+
+The `NeedsRehash` method provided by the Hash facade allows you to determine if the work factor used by the hasher has changed since the password was hashed. Some applications choose to perform this check during the application's authentication process:
+
+```go
+if facades.Hash.NeedsRehash(hashed) {
+ hashed = facades.Hash.Make('plain-text');
+}
+```
diff --git a/the-basics/grpc.md b/the-basics/grpc.md
index 58afdce..7b6a5cc 100644
--- a/the-basics/grpc.md
+++ b/the-basics/grpc.md
@@ -156,7 +156,8 @@ func init() {
// Interceptors can be the group name of UnaryClientInterceptorGroups in app/grpc/kernel.go.
"clients": map[string]any{
"user": map[string]any{
- "host": config.Env("GRPC_HOST", ""),
+ "host": config.Env("GRPC_USER_HOST", ""),
+ "port": config.Env("GRPC_USER_PORT", ""),
"interceptors": []string{"trace"},
},
},
diff --git a/the-basics/logging.md b/the-basics/logging.md
index 7a207d0..b50f5f1 100644
--- a/the-basics/logging.md
+++ b/the-basics/logging.md
@@ -12,6 +12,8 @@ Make custom configurations in `config/logging.go`, allows to configure different
`Goravel` uses `stack` channel to record logs by default, `stack` allows logs to be forwarded to multiple channels.
+The `print` configuration in `single` and `daily` drivers can control log output to console.
+
## Available channel drivers
| Name | Description |
diff --git a/the-basics/middleware.md b/the-basics/middleware.md
index df37d73..2f36fa4 100644
--- a/the-basics/middleware.md
+++ b/the-basics/middleware.md
@@ -6,7 +6,7 @@
Middleware can filtering HTTP requests that enter the application. For example, `Goravel` provides a CORS middleware, which can implement requests across domains.
-## Define Middlewares
+## Define Middleware
You can create your own middleware in the `app/http/middleware` directory, the structure is as follows.
@@ -43,18 +43,19 @@ func Cors() http.Middleware {
There are some middleware available in Goravel:
-| Middlewares | Action |
+| Middleware | Action |
| ------------------------------------------------- | ------------- |
| github.com/goravel/framework/http/middleware/Cors | across domain |
+| github.com/goravel/framework/http/middleware/Throttle | Rate Limiting |
### Create Middleware By Command
```
go run . artisan make:middleware Cors
```
-## Register Middlewares
+## Register Middleware
-### Global Middlewares
+### Global Middleware
If you want to apply middleware for every HTTP request of your application, you only need to register the middleware in the `Middleware` in the `app/http/kernel.go` file.
@@ -77,7 +78,7 @@ func (kernel *Kernel) Middleware() []http.Middleware {
}
```
-### Assign Middlewares for Routing
+### Assign Middleware for Routing
You can register the middleware for some routing separately:
diff --git a/the-basics/request.md b/the-basics/request.md
index 6bf9acd..64a8243 100644
--- a/the-basics/request.md
+++ b/the-basics/request.md
@@ -57,18 +57,26 @@ method := ctx.Request().Ip()
## Input
-### Retrieving An Input Value
+### Retrieving An Route Value
```go
// /users/{id}
-id := ctx.Request().Input("id")
+id := ctx.Request().Route("id")
+id := ctx.Request().RouteInt("id")
+id := ctx.Request().RouteInt64("id")
```
### Retrieving Input From The Query String
```go
// /users?name=goravel
-name := ctx.Request().Query("name", "goravel")
+name := ctx.Request().Query("name")
+name := ctx.Request().Query("name", "default")
+
+// /users?id=1
+name := ctx.Request().QueryInt("id")
+name := ctx.Request().QueryInt64("id")
+name := ctx.Request().QueryBool("id")
// /users?names=goravel1&names=goravel2
names := ctx.Request().QueryArray("names")
@@ -80,9 +88,31 @@ names := ctx.Request().QueryMap("names")
### Retrieving Form
```go
+name := ctx.Request().Form("name")
name := ctx.Request().Form("name", "goravel")
```
+### Retrieving Json
+
+```go
+name := ctx.Request().Json("name")
+name := ctx.Request().Json("name", "goravel")
+```
+
+> Note: Only one-dimensional Json data can be obtained, otherwise it will return empty.
+
+### Retrieving An Input Value
+
+Access all of the user input without worrying about which HTTP verb was used for the request. Retrieve order: `json`, `form`, `query`, `route`.
+
+```go
+name := ctx.Request().Input("name")
+name := ctx.Request().Json("name", "goravel")
+name := ctx.Request().InputInt("name")
+name := ctx.Request().InputInt64("name")
+name := ctx.Request().InputBool("name")
+```
+
### Json/Form Bind Struct
```go
@@ -94,6 +124,11 @@ var user User
err := ctx.Request().Bind(&user)
```
+```go
+var user map[string]any
+err := ctx.Request().Bind(&user)
+```
+
## File
### Retrieving File
diff --git a/the-basics/routing.md b/the-basics/routing.md
index e6dbad7..25d7eb6 100644
--- a/the-basics/routing.md
+++ b/the-basics/routing.md
@@ -14,7 +14,7 @@ You can add routing files under the `routes` directory to perform more fine-grai
## Start HTTP Server
-Start the HTTP server in `main.go` in the root directory.
+Start the HTTP server in `main.go` in the root directory. `facades.Route.Run()` will automatically fetch the `route.host` configuration.
```go
package main
@@ -31,7 +31,7 @@ func main() {
//Start http server by facades.Route.
go func() {
- if err := facades.Route.Run(facades.Config.GetString("app.host")); err != nil {
+ if err := facades.Route.Run(); err != nil {
facades.Log.Errorf("Route run error: %v", err)
}
}()
@@ -51,17 +51,28 @@ Framework has a general middleware built in, you can also customize it according
import "github.com/goravel/framework/http/middleware"
func (kernel *Kernel) Middleware() []http.Middleware {
- return []http.Middleware{
- middleware.Tls(facades.Config.GetString("app.host")),
- }
+ return []http.Middleware{
+ middleware.Tls(),
+ }
}
```
### Start Server
+`facades.Route.RunTLS()` will automatically fetch the `route.tls` configuration:
+
+```go
+// main.go
+if err := facades.Route.RunTLS(); err != nil {
+ facades.Log.Errorf("Route run error: %v", err)
+}
+```
+
+You can also use `facades.Route.RunTLSWithCert()` method to customize host and certificate.
+
```go
// main.go
-if err := facades.Route.RunTLS(facades.Config.GetString("app.host"), "ca.pem", "ca.key"); err != nil {
+if err := facades.Route.RunTLSWithCert("127.0.0.1:3000", "ca.pem", "ca.key"); err != nil {
facades.Log.Errorf("Route run error: %v", err)
}
```
@@ -71,6 +82,8 @@ if err := facades.Route.RunTLS(facades.Config.GetString("app.host"), "ca.pem", "
| Methods | Action |
| ---------- | --------------------------------------- |
| Run | [Start HTTP Server](#Start-HTTP-Server) |
+| RunTLS | [Start HTTPS Server](#Start-HTTPS-Server) |
+| RunTLSWithCert | [Start HTTPS Server](#Start-HTTPS-Server) |
| Group | [Group Routing](#Group-Routing) |
| Prefix | [Routing Prefix](#Routing-Prefix) |
| ServeHTTP | [Testing Routing](#Testing-Routing) |
@@ -146,6 +159,103 @@ facades.Route.Middleware(middleware.Cors()).Get("users", userController.Show)
Detail [Middleware](./middleware.md)
+## Rate Limiting
+
+### Defining Rate Limiters
+
+Goravel includes powerful and customizable rate limiting services that you may utilize to restrict the amount of traffic for a given route or group of routes. To get started, you should define rate limiter configurations that meet your application's needs. Typically, this should be done within the `configureRateLimiting` method of your application's `app/providers/route_service_provider.go` class.
+
+Rate limiters are defined using the `facades.RateLimiter`s `For` method. The `For` method accepts a rate limiter name and a closure that returns the limit configuration that should apply to routes that are assigned to the rate limiter. The rate limiter name may be any string you wish:
+
+```go
+import (
+ contractshttp "github.com/goravel/framework/contracts/http"
+ "github.com/goravel/framework/facades"
+ "github.com/goravel/framework/http/limit"
+)
+
+func (receiver *RouteServiceProvider) configureRateLimiting() {
+ facades.RateLimiter.For("global", func(ctx contractshttp.Context) contractshttp.Limit {
+ return limit.PerMinute(1000)
+ })
+}
+```
+
+If the incoming request exceeds the specified rate limit, a response with a 429 HTTP status code will automatically be returned by Goravel. If you would like to define your own response that should be returned by a rate limit, you may use the response method:
+
+```go
+facades.RateLimiter.For("global", func(ctx contractshttp.Context) contractshttp.Limit {
+ return limit.PerMinute(1000).Response(func(ctx contractshttp.Context) {
+ ctx.Response().String(429, "Custom response...")
+ return
+ })
+})
+```
+
+Since rate limiter callbacks receive the incoming HTTP request instance, you may build the appropriate rate limit dynamically based on the incoming request or authenticated user:
+
+```go
+facades.RateLimiter.For("global", func(ctx contractshttp.Context) contractshttp.Limit {
+ // Suppose
+ if is_vip() {
+ return limit.PerMinute(100)
+ }
+
+ return nil
+})
+```
+
+#### Segmenting Rate Limits
+
+Sometimes you may wish to segment rate limits by some arbitrary value. For example, you may wish to allow users to access a given route 100 times per minute per IP address. To accomplish this, you may use the `By` method when building your rate limit:
+
+```go
+facades.RateLimiter.For("global", func(ctx contractshttp.Context) contractshttp.Limit {
+ if is_vip() {
+ return limit.PerMinute(100).By(ctx.Request().Ip())
+ }
+
+ return nil
+})
+```
+
+To illustrate this feature using another example, we can limit access to the route to 100 times per minute per authenticated user ID or 10 times per minute per IP address for guests:
+
+```go
+facades.RateLimiter.For("global", func(ctx contractshttp.Context) contractshttp.Limit {
+ if userID != 0 {
+ return limit.PerMinute(100).By(userID)
+ }
+
+ return limit.PerMinute(100).By(ctx.Request().Ip())
+})
+```
+
+#### Multiple Rate Limits
+
+If needed, you may return an array of rate limits for a given rate limiter configuration. Each rate limit will be evaluated for the route based on the order they are placed within the array:
+
+```go
+facades.RateLimiter.ForWithLimits("login", func(ctx contractshttp.Context) []contractshttp.Limit {
+ return []contractshttp.Limit{
+ limit.PerMinute(500),
+ limit.PerMinute(100).By(ctx.Request().Ip()),
+ }
+})
+```
+
+### Attaching Rate Limiters To Routes
+
+Rate limiters may be attached to routes or route groups using the throttle middleware. The throttle middleware accepts the name of the rate limiter you wish to assign to the route:
+
+```go
+facades.Route.Middleware(middleware.Throttle("global")).Get("/", func(ctx http.Context) {
+ ctx.Response().Json(200, http.Json{
+ "Hello": "Goravel",
+ })
+})
+```
+
## Cross-Origin Resource Sharing (CORS)
Goravel has CORS enabled by default, the configuration can be modified in `config/cors.go`, the funciton is registered in `app/http/kernel.go` as global middleware.
diff --git a/the-basics/validation.md b/the-basics/validation.md
index 341018c..ffdd90d 100644
--- a/the-basics/validation.md
+++ b/the-basics/validation.md
@@ -94,12 +94,39 @@ The generated form request class will be placed in the `app/http/requests` direc
As you might have guessed, the `Authorize` method is responsible for determining if the currently authenticated user can perform the action represented by the request, while the `Rules` method returns the validation rules that should apply to the request's data:
```go
-func (r *StorePostRequest) Rules() map[string]string {
+package requests
+
+import (
+ "github.com/goravel/framework/contracts/http"
+ "github.com/goravel/framework/contracts/validation"
+)
+
+type StorePostRequest struct {
+ Name string `form:"name" json:"name"`
+}
+
+func (r *StorePostRequest) Authorize(ctx http.Context) error {
+ return nil
+}
+
+func (r *StorePostRequest) Rules(ctx http.Context) map[string]string {
return map[string]string{
- "title": "required|max_len:255",
- "body": "required",
+ // The key are consistent with the incoming key.
+ "name": "required|max_len:255",
}
}
+
+func (r *StorePostRequest) Messages(ctx http.Context) map[string]string {
+ return map[string]string{}
+}
+
+func (r *StorePostRequest) Attributes(ctx http.Context) map[string]string {
+ return map[string]string{}
+}
+
+func (r *StorePostRequest) PrepareForValidation(ctx http.Context, data validation.Data) error {
+ return nil
+}
```
So, how are the validation rules evaluated? All you need to do is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:
@@ -111,9 +138,11 @@ func (r *PostController) Store(ctx http.Context) {
}
```
+> Note that since `form` passed values are of `string` type by default, all fields in request should also be of `string` type, otherwise please use `JSON` to pass values.
+
### Authorizing Form Requests
-The form request class also contains an `Authorize` method. Within this method, you may determine if the authenticated user actually has the authority to update a given resource. For example, you may determine if a user actually owns a blog comment they are attempting to update. Most likely, you will interact with your [authorization gates and policies](../digging-deeper/authorization.md) within this method:
+The form request class also contains an `Authorize` method. Within this method, you may determine if the authenticated user actually has the authority to update a given resource. For example, you may determine if a user actually owns a blog comment they are attempting to update. Most likely, you will interact with your [authorization gates and policies](../security/authorization.md) within this method:
```go
func (r *StorePostRequest) Authorize(ctx http.Context) error {
diff --git a/upgrade/v1.10.md b/upgrade/v1.10.md
new file mode 100644
index 0000000..369e4c4
--- /dev/null
+++ b/upgrade/v1.10.md
@@ -0,0 +1,226 @@
+# Upgrading To v1.10 From v1.9
+
+[[toc]]
+
+## Exciting New Features 🎉
+
+- [Add facades.Crypt(1.10.0)](#Encryption)
+- [Add facades.Hash(1.10.0)](#Hashing)
+- [Add Rate Limiting For Routing(1.10.0)](#Add-Rate-Limiting-For-Routing)
+
+## Enhancements 🚀
+
+- [Optimize HTTP startup mode(1.10.0)](#Optimize-HTTP-startup-mode)
+- [Optimize GRPC startup mode(1.10.0)](#Optimize-GRPC-startup-mode)
+- [Add configuration to control log output to console(1.10.0)](#Add-configuration-to-control-log-output-to-console)
+- [Request modify and add methods(1.10.0)](#Request-modify-and-add-methods)
+- [Queue support delayed dispatching(1.10.0)](#Queue-support-delayed-dispatching)
+- [The Connection in ORM supports set table prefix and singular(1.10.0)](#The-Connection-in-ORM-supports-set-table-prefix-and-singular)
+- [Add docker-compose.yml file(1.10.0)](#Add-docker-compose.yml-file)
+- [Optimize Orm(1.10.0)](#Optimize-Orm)
+- [Support multiple SQL in migration file(1.10.0)](#Support-multiple-SQL-in-migration-file)
+- [Add minio driver for File Storage(1.10.0)](#Add-minio-driver-for-File-Storage)
+- [contracts/http add status mapping of net/http(1.10.0)](#contracts-http-add-status-mapping-of-net/http)
+
+## Breaking Changes 🛠
+
+- [APP_KEY required(1.10.0)](#APP_KEY-required)
+- [Add ctx parameter to the methods under Form Request(1.10.0)](#Add-ctx-parameter-to-the-methods-under-Form-Request)
+- [facades.Auth.Parse adds payload returns(1.10.0)](#facades.Auth.Parse-add-payload-returns)
+- [Some methods of Orm add new return values(1.10.0)](#Some-methods-of-Orm-add-new-return-values)
+
+## Upgrade Guide
+
+**Estimated Upgrade Time: 20 Minutes**
+
+### Updating Dependencies
+
+Update dependencies in the `go.mod` file:
+
+```
+go get -u github.com/goravel/framework@v1.10.0
+```
+
+### Encryption
+
+Version: v1.10.0
+
+Add `facades.Crypt`:
+
+1. add `&crypt.ServiceProvider{}` to the `providers` item in the [config/app.go](https://github.com/goravel/goravel/blob/v1.10.x/config/app.go) file;
+
+[For Detail](../security/encryption.md)
+
+### Hashing
+
+Version: v1.10.0
+
+Add `facades.Hash`:
+
+1. add `&hash.ServiceProvider{}` to the `providers` item in the [config/app.go](https://github.com/goravel/goravel/blob/v1.10.x/config/app.go) file;
+
+2. Add [config/hashing.go](https://github.com/goravel/goravel/blob/v1.10.x/config/hashing.go) file;
+
+[For Detail](../security/hashing.md)
+
+### Add Rate Limiting For Routing
+
+Version: v1.10.0
+
+[For Detail](../the-basics/routing.md#Rate-Limiting)
+
+### Optimize HTTP startup mode
+
+Version: v1.10.0
+
+1. Add `config/http.go` configuration, [For Detail](https://github.com/goravel/goravel/blob/v1.10.x/config/http.go);
+2. The `facades.Route.Run` method no longer needs to pass parameters, by default use `http.host` and `http.port`(you don't need to modify the code, it's backward compatible);
+3. The `facades.Route.RunTLS` method no longer needs to pass parameters, by default use `http.tls.host`, `http.tls.port`, `http.tls.ssl.cert` and `http.tls.ssl.key`, if you are using it, you need to modify the code;
+4. Add `facades.Route.RunTLSWithCert` method, [For Detail](../the-basics/routing.md#start-server);
+5. Move `app.url`, `app.host` to `http.url`, `http.host`;
+
+### Optimize GRPC startup mode
+
+Version: v1.10.0
+
+The `facades.Grpc.Run` method no longer needs to pass parameters, by default use `grpc.host` and `grpc.port`(you don't need to modify the code, it's backward compatible);
+
+### Add configuration to control log output to console
+
+Version: v1.10.0
+
+Add `print` configuration to `single`, `daily` channel in the `config/logging.go` file, it can control log output to console, [For Detail](https://github.com/goravel/goravel/blob/v1.10.x/config/logging.go);
+
+### Request modify and add methods
+
+Version: v1.10.0
+
+1. The `Input` method is changed from only getting routing parameters to getting data according to the following order: `json`, `form`, `query`, `route`。Note: `json` can only get one-dimensional data, otherwise it will return empty;
+2. Add `Route` method to replace the original `Input` method;
+3. The default value of `Query` and `Form` methods are modified to be unnecessary;
+4. Add methods as shown below:
+
+| Method | Action |
+| ----------- | -------------- |
+| Route | [Retrieving An Route Value](../the-basics/request.md#Retrieving-An-Input-Value) |
+| RouteInt | [Retrieving An Route Value](../the-basics/request.md#Retrieving-An-Input-Value) |
+| RouteInt64 | [Retrieving An Route Value](../the-basics/request.md#Retrieving-An-Input-Value) |
+| QueryInt | [Retrieving Input From The Query String](../the-basics/request.md#Retrieving-Input-From-The-Query-String) |
+| QueryInt64 | [Retrieving Input From The Query String](../the-basics/request.md#Retrieving-Input-From-The-Query-String) |
+| QueryBool | [Retrieving Input From The Query String](../the-basics/request.md#Retrieving-Input-From-The-Query-String) |
+| InputInt | [Retrieving An Input Value](../the-basics/request.md#Retrieving-An-Input-Value) |
+| InputInt64 | [Retrieving An Input Value](../the-basics/request.md#Retrieving-An-Input-Value) |
+| InputBool | [Retrieving An Input Value](../the-basics/request.md#Retrieving-An-Input-Value) |
+| Json | [Retrieving Json](../the-basics/request.md#Retrieving-Json) |
+
+### Queue support delayed dispatching
+
+Version: v1.10.0
+
+Add `Delay` method, [For Detail](../digging-deeper/queues.md#Delayed-Dispatching)
+
+### The Connection in ORM supports set table prefix and singular
+
+Version: v1.10.0
+
+1. `Model` supports specify table name, [For Detail](../orm/getting-started.md#指定表名);
+2. Add new keys to `connection` of `config/database.go`:
+
+`prefix`: Set prefix for table name;
+`singular`: Set the table name to use singular or plural;
+
+[For Detail](https://github.com/goravel/goravel/blob/v1.10.x/config/database.go)
+
+### Add docker-compose.yml file
+
+Version: v1.10.0
+
+You can quickly start the service with the following command:
+
+```
+docker-compose build
+docker-compose up
+```
+
+### Optimize Orm
+
+Version: v1.10.0
+
+1. Add the following methods:
+
+| Functions | Action |
+| ------------- | ------------------------------------------------------- |
+| FirstOr | [Query or return data through callback](#Query-one-line) |
+| FirstOrNew | [Retrieving Or New Models](#Retrieving-Or-Creating-Models) |
+| FirstOrFail | [Not Found Error](#Not-Found-Error) |
+| UpdateOrCreate | [Update or create](#Update-or-create) |
+
+2. An error was reported like this before, but now it's supported:
+
+```go
+query := facades.Orm.Query()
+query = query.Where()
+```
+
+### Support multiple SQL in migration file
+
+Version: v1.10.0
+
+Previously, only one SQL statement was supported in the migration file, but now multiple statements are supported.
+
+### Add minio driver for File Storage
+
+Version: v1.10.0
+
+Add minio configuration, [For Detail](https://github.com/goravel/goravel/blob/v1.10.x/config/filesystems.go).
+
+### contracts/http add status mapping of net/http
+
+Version: v1.10.0
+
+You can use status codes such as `http.StatusOK` directly in controller without importing `net/http`.
+
+[For Detail](https://github.com/goravel/framework/blob/v1.10.0/contracts/http/status.go)
+
+### APP_KEY required
+
+Version: v1.10.0
+
+The `APP_KEY` in the `.env` file is changed to required, you can run command to generate the APP_KEY: `go run . artisan key:generate`.
+
+### Add ctx parameter to the methods under Form Request
+
+Version: v1.10.0
+
+Add `ctx http.Context` parameter to the methods under Form Request: `Rules`, `Messages`, `Attributes`, `PrepareForValidation`, allows you to do more custom configurations.
+
+[For Detail](../the-basics/validation.md#creating-form-requests)
+
+### facades.Auth.Parse add payload returns
+
+Version: v1.10.0
+
+`err := facades.Auth.Parse(ctx, token)` change to `payload, err := facades.Auth.Parse(ctx, token)`, through `payload` you can get:
+
+1. `Guard`: Current Guard;
+2. `Key`: User flag;
+3. `ExpireAt`: Expire time;
+4. `IssuedAt`: Issued time;
+
+[For Detail](../security/authentication.md#parse-token)
+
+### Some methods of Orm add new return values
+
+Version: v1.10.0
+
+The following methods add `*Result` return value to get the number of affected rows:
+
+```go
+res, err := query.Delete(&user)
+res, err := query.Exec(fmt.Sprintf("DELETE FROM users where id = %d", user.ID))
+res, err := query.ForceDelete(&User{})
+res, err := query.Updates(User{Avatar: "avatar"})
+
+// Affected rows
+num := res.RowsAffected
+```
diff --git a/upgrade/v1.6.md b/upgrade/v1.6.md
index 65d2f7e..6d6534a 100644
--- a/upgrade/v1.6.md
+++ b/upgrade/v1.6.md
@@ -27,4 +27,4 @@ github.com/goravel/framework v1.6.3
1. Add [app/providers/auth_service_provider.go](https://github.com/goravel/goravel/blob/v1.6.0/app/providers/auth_service_provider.go) file;
3. Add `&providers.AuthServiceProvider{}` to the `providers` item in the [config/app.go](https://github.com/goravel/goravel/blob/v1.6.0/config/app.go) file;
-[For Detail](../digging-deeper/authorization.md)
+[For Detail](../security/authorization.md)
diff --git a/upgrade/v1.7.md b/upgrade/v1.7.md
index cc777f2..742501e 100644
--- a/upgrade/v1.7.md
+++ b/upgrade/v1.7.md
@@ -34,7 +34,7 @@ github.com/goravel/framework v1.7.3
Version: v1.7.0
1. Add [app/providers/validation_service_provider.go](https://github.com/goravel/goravel/blob/v1.7.0/app/providers/validation_service_provider.go) file;
-3. Add `&providers.AuthServiceProvider{}`, `&providers.ValidationServiceProvider{},` to the `providers` item in the [config/app.go](https://github.com/goravel/goravel/blob/v1.7.0/config/app.go) file;
+3. Add `&validation.ServiceProvider{}`, `&providers.ValidationServiceProvider{},` to the `providers` item in the [config/app.go](https://github.com/goravel/goravel/blob/v1.7.0/config/app.go) file;
[For Detail](../the-basics/validation.md)
diff --git a/upgrade/v1.8.md b/upgrade/v1.8.md
index 5d1191c..2b5c009 100644
--- a/upgrade/v1.8.md
+++ b/upgrade/v1.8.md
@@ -45,12 +45,12 @@ Add methods for Orm, to handle model association:
| Method | Action |
| ----------- | --------------------------------- |
-| Association | [Association](../ORM/association.md#Find-Associations) |
-| DB | [Generic Database Interface sql.DB](../ORM/getting-started.md#Generic-Database-Interface-sql.DB) |
-| Load | [Lazy Eager Loading](../ORM/association.md#Lazy-Eager-Loading) |
-| LoadMissing | [Lazy Eager Loading(not exist)](../ORM/association.md#Lazy-Eager-Loading) |
-| Omit | [Omit associations](../ORM/association.md#Create-or-Update-Associations) |
-| With | [Eager Loading](../ORM/association.md#Eager-Loading) |
+| Association | [Association](../orm/association.md#Find-Associations) |
+| DB | [Generic Database Interface sql.DB](../orm/getting-started.md#Generic-Database-Interface-sql.DB) |
+| Load | [Lazy Eager Loading](../orm/association.md#Lazy-Eager-Loading) |
+| LoadMissing | [Lazy Eager Loading(not exist)](../orm/association.md#Lazy-Eager-Loading) |
+| Omit | [Omit associations](../orm/association.md#Create-or-Update-Associations) |
+| With | [Eager Loading](../orm/association.md#Eager-Loading) |
### Add methods for Request
@@ -81,9 +81,9 @@ The import order in the `bootstrap/app.go` file change to:
package bootstrap
import (
- "github.com/goravel/framework/foundation"
+ "github.com/goravel/framework/foundation"
- "goravel/config"
+ "goravel/config"
)
```
diff --git a/upgrade/v1.9.md b/upgrade/v1.9.md
index 962542b..9a5a174 100644
--- a/upgrade/v1.9.md
+++ b/upgrade/v1.9.md
@@ -20,6 +20,7 @@
- [File gets the wrong file type(1.9.0)](#File-gets-the-wrong-file-type)
- [Fix template error on make:event and make:listener command(1.9.0)](#Fix-template-error-on-make:event-command)
+- [Fix some types cannot obtain suffixes when save file(1.9.1)](#Fix-some-types-cannot-obtain-suffixes-when-save-file)
## Dependency Updates ⬆️
@@ -41,7 +42,7 @@ go get -u github.com/goravel/framework@v1.9.0
Version: v1.9.0
-Database supports read-write separation,[For Detail](../ORM/getting-started.md#read--write-connections).
+Database supports read-write separation,[For Detail](../orm/getting-started.md#read--write-connections).
### Add database pool configuration
@@ -82,7 +83,7 @@ Version: v1.9.0
Version: v1.9.0
-`facades.Orm` add `Paginate` method, [For detail](../ORM/getting-started.md#Paginate).
+`facades.Orm` add `Paginate` method, [For detail](../orm/getting-started.md#Paginate).
### Add make command
@@ -101,6 +102,8 @@ go run . artisan make:model User
### Add new methods for Response
+Version: v1.9.0
+
| Method | Action |
| ----------- | -------------- |
| Data | [Custom return](../the-basics/response.md#custom-return) |
@@ -120,4 +123,17 @@ Fix the problem that `.docx`, `.xlsx`, etc. are incorrectly identified as `.zip`
### Fix template error on make:event and make:listener command
+Version: v1.9.0
+
`import "github.com/goravel/framework/contracts/events"` 改为 `import "github.com/goravel/framework/contracts/event"`
+
+### Fix some types cannot obtain suffixes when save file
+
+Version: v1.9.1
+
+Some types cannot obtain suffixes through the code shown below:
+
+```go
+file, err := ctx.Request().File()
+file.Store("upload")
+```
diff --git a/zh/ORM/getting-started.md b/zh/ORM/getting-started.md
index 8cf8d47..8316d09 100644
--- a/zh/ORM/getting-started.md
+++ b/zh/ORM/getting-started.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
Goravel 提供了一套非常简单易用的数据库交互方式,开发者可以使用 `facades.Orm` 进行操作。目前,Goravel 为以下四种数据库提供了官方支持:
@@ -77,6 +77,27 @@ import "github.com/goravel/framework/contracts/database"
go run . artisan make:model User
```
+### 指定表名
+
+```go
+package models
+
+import (
+ "github.com/goravel/framework/database/orm"
+)
+
+type User struct {
+ orm.Model
+ Name string
+ Avatar string
+ orm.SoftDeletes
+}
+
+func (r *User) TableName() string {
+ return "goravel_user"
+}
+```
+
## facades.Orm 可用方法
| 方法名 | 作用 |
@@ -99,11 +120,14 @@ go run . artisan make:model User
| Distinct | [过滤重复](#过滤重复) |
| Driver | [获取当前驱动](#获取当前驱动) |
| Exec | [执行原生更新 SQL](#执行原生更新SQL) |
-| Find | [获取一条数据](#查询) |
-| First | [获取一条数据](#查询) |
-| FirstOrCreate | [获取或创建一条数据](#查询) |
+| Find | [查询一条或多条数据](#根据-ID-查询单条或多条数据) |
+| First | [查询一条数据](#查询一条数据) |
+| FirstOr | [查询或通过回调返回一条数据](#查询一条数据) |
+| FirstOrCreate | [查询或创建模型](#查询或创建模型) |
+| FirstOrNew | [查询或实例化模型](#查询或创建模型) |
+| FirstOrFail | [未找到时抛出错误](#未找到时抛出错误) |
| ForceDelete | [强制删除](#删除) |
-| Get | [获取多条数据](#查询) |
+| Get | [查询多条数据](#查询多条数据) |
| Group | [Group 查询](#Group-By-&-Having) |
| Having | [Having 查询](#Group-By-&-Having) |
| Join | [Join 查询](#Join查询) |
@@ -114,15 +138,16 @@ go run . artisan make:model User
| OrWhere | [查询条件](#Where条件) |
| Paginate | [分页](#分页) |
| Pluck | [查询单列](#查询单列) |
-| Raw | [执行原生查询 SQL](#执行原生查询SQL) |
+| Raw | [执行原生查询 SQL](#执行原生查询-SQL) |
| Rollback | [手动回滚事务](#事务) |
| Save | [保存修改](#更新) |
-| Scan | [将数据解析到 struct](#执行原生查询SQL) |
+| Scan | [将数据解析到 struct](#执行原生查询-SQL) |
| Scopes | [Scopes](#Execute-Native-SQL) |
| Select | [指定查询列](#指定查询列) |
| Table | [指定表](#指定表查询) |
| Update | [更新单个字段](#更新) |
| Updates | [更新多个字段](#更新) |
+| UpdateOrCreate | [更新或创建一条数据](#更新或创建一条数据) |
| Where | [查询条件](#Where条件) |
| WithTrashed | [查询软删除](#查询软删除) |
@@ -181,7 +206,7 @@ facades.Orm.WithContext(ctx).Query()
### 查询
-查询单条数据
+#### 查询一条数据
```go
var user models.User
@@ -189,7 +214,17 @@ facades.Orm.Query().First(&user)
// SELECT * FROM users WHERE id = 10;
```
-根据 ID 查询单条或多条数据
+有时你可能希望检索查询的第一个结果或在未找到结果时执行一些其他操作。`firstOr` 方法将返回匹配查询的第一个结果,或者,如果没有找到结果,则执行给定的闭包。你可以在闭包中对模型进行赋值:
+
+```go
+facades.Orm.Query().Where("name", "first_user").FirstOr(&user, func() error {
+ user.Name = "goravel"
+
+ return nil
+})
+```
+
+#### 根据 ID 查询单条或多条数据
```go
var user models.User
@@ -200,7 +235,7 @@ facades.Orm.Query().Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);
```
-当用户表主键为 `string` 类型,调用 `Find` 方法时需要指定主键
+#### 当用户表主键为 `string` 类型,调用 `Find` 方法时需要指定主键
```go
var user models.User
@@ -208,7 +243,7 @@ facades.Orm.Query().Find(&user, "uuid=?" ,"a")
// SELECT * FROM users WHERE uuid = "a";
```
-查询多条数据
+#### 查询多条数据
```go
var users []models.User
@@ -216,17 +251,38 @@ facades.Orm.Query().Where("id in ?", []int{1,2,3}).Get(&users)
// SELECT * FROM users WHERE id IN (1,2,3);
```
-查找或创建
+#### 查询或创建模型
+
+`FirstOrCreate` 方法将尝试使用给定的列 / 值对来查找数据库记录。如果在数据库中找不到该模型,则将插入一条记录,其中包含将第二个参数与可选的第三个参数合并后产生的属性:
+
+`FirstOrNew` 方法,类似 `FirstOrCreate`,会尝试在数据库中找到与给定属性匹配的记录。如果没有找到,则会返回一个新的模型实例。请注意,由 `FirstOrNew` 返回的模型尚未持久化到数据库中。需要手动调用 `Save` 方法来保存它:
```go
var user models.User
-facades.Orm.Query().Where("sex = ?", 1).FirstOrCreate(&user, models.User{Name: "tom"})
+facades.Orm.Query().Where("sex", 1).FirstOrCreate(&user, models.User{Name: "tom"})
// SELECT * FROM users where name="tom" and sex=1;
// INSERT INTO users (name) VALUES ("tom");
-facades.Orm.Query().Where("sex = ?", 1).FirstOrCreate(&user, models.User{Name: "tom"}, , models.User{Avatar: "avatar"})
+facades.Orm.Query().Where("sex", 1).FirstOrCreate(&user, models.User{Name: "tom"}, models.User{Avatar: "avatar"})
// SELECT * FROM users where name="tom" and sex=1;
// INSERT INTO users (name,avatar) VALUES ("tom", "avatar");
+
+var user models.User
+facades.Orm.Query().Where("sex", 1).FirstOrNew(&user, models.User{Name: "tom"})
+// SELECT * FROM users where name="tom" and sex=1;
+
+facades.Orm.Query().Where("sex", 1).FirstOrNew(&user, models.User{Name: "tom"}, models.User{Avatar: "avatar"})
+// SELECT * FROM users where name="tom" and sex=1;
+```
+
+#### 未找到时抛出错误
+
+当找不到模型时,`First` 方法不会抛出错误,如果想抛出,可以使用 `FirstOrFail`:
+
+```go
+var user models.User
+err := facades.Orm.Query().FirstOrFail(&user)
+// err == orm.ErrRecordNotFound
```
### Where 条件
@@ -366,7 +422,7 @@ result := facades.Orm.Query().Create(&users)
### 更新
-在现有模型基础上进行更新
+#### 在现有模型基础上进行更新
```go
var user models.User
@@ -378,21 +434,32 @@ facades.Orm.Query().Save(&user)
// UPDATE users SET name='tom', age=100, updated_at = '2022-09-28 16:28:22' WHERE id=1;
```
-更新单一字段
+#### 更新单一字段
```go
-facades.Orm.Query().Model(&models.User{}).Where("name = ?", "tom").Update("name", "hello")
+facades.Orm.Query().Model(&models.User{}).Where("name", "tom").Update("name", "hello")
// UPDATE users SET name='tom', updated_at='2022-09-28 16:29:39' WHERE name="tom";
```
-更新多个字段
+#### 更新多个字段
```go
-facades.Orm.Query().Model(&user).Where("name = ?", "tom").Updates(User{Name: "hello", Age: 18})
+facades.Orm.Query().Model(&user).Where("name", "tom").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name="hello", age=18, updated_at = '2022-09-28 16:30:12' WHERE name = "tom";
```
-> 当使用 `struct` 进行批量更新(Updates)时,Orm 只会更新非零值的字段。你可以使用 `map` 更新字段,或者使用 `Select` 指定要更新的字段。
+> 当使用 `struct` 进行批量更新(Updates)时,Orm 只会更新非零值的字段。你可以使用 `map` 更新字段,或者使用 `Select` 指定要更新的字段。注意 `struct` 只能为 `Model`,如果想用非 `Model` 批量更新,需要使用 `.Table("users")`,但此时无法自动更新 `updated_at` 字段。
+
+#### 更新或创建一条数据
+
+根据 `name` 查询,如果不存在,则根据 `name`, `avatar` 创建,如果存在,则根据 `name` 更新 `avatar`:
+
+```go
+facades.Orm.Query().UpdateOrCreate(&user, User{Name: "name"}, User{Avatar: "avatar"})
+// SELECT * FROM `users` WHERE `users`.`name` = 'name' AND `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1
+// INSERT INTO `users` (`created_at`,`updated_at`,`deleted_at`,`name`,`avatar`) VALUES ('2023-03-11 10:11:08.869','2023-03-11 10:11:08.869',NULL,'name','avatar')
+// UPDATE `users` SET `avatar`='avatar',`updated_at`='2023-03-11 10:11:08.881' WHERE `name` = 'name' AND `users`.`deleted_at` IS NULL AND `id` = 1
+```
### 删除
diff --git a/zh/ORM/migrations.md b/zh/ORM/migrations.md
index b3351e8..3a9691e 100644
--- a/zh/ORM/migrations.md
+++ b/zh/ORM/migrations.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
当多人协作开发应用程序时,如果同步数据库结构没有一个统一的规范,以保证所有人的本地数据都是一致的,那将是灾难。数据库迁移就是为了解决这个问题,将数据库的结构进行版本控制,以保证所有开发人员的数据库结构的一致性。
diff --git a/zh/ORM/relationships.md b/zh/ORM/relationships.md
index 92b07bb..106e8af 100644
--- a/zh/ORM/relationships.md
+++ b/zh/ORM/relationships.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
数据库表通常相互关联。例如,一篇博客文章可能有许多评论,或者一个订单对应一个下单用户。Orm 让这些关联的管理和使用变得简单,并支持多种常用的关联类型:
diff --git a/zh/README.md b/zh/README.md
index 862bb99..653593e 100644
--- a/zh/README.md
+++ b/zh/README.md
@@ -29,17 +29,12 @@ Goravel 是一个功能完备、具有良好扩展能力的 Web 应用程序框
- [x] 邮件
- [x] 表单验证
- [x] Mock
+- [x] Hash
+- [x] Crypt
## 路线图
-- [ ] Hash
-- [ ] Crypt
-- [ ] Websocket 支持
-- [ ] 广播系统
-- [ ] 延迟队列
-- [ ] 队列支持 DB 驱动
-- [ ] 消息通知
-- [ ] 完善单元测试
+[For Detail](https://github.com/goravel/goravel/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
## 文档
@@ -49,6 +44,16 @@ Goravel 是一个功能完备、具有良好扩展能力的 Web 应用程序框
> 优化文档,请提交 PR 至文档仓库 [https://github.com/goravel/docs](https://github.com/goravel/docs)
+## Contributors
+
+这个项目的存在要归功于所有做出贡献的人。
+
+
+
+
+
+
+
## 群组
微信入群,请备注 Goravel
diff --git a/zh/architecutre-concepts/facades.md b/zh/architecutre-concepts/facades.md
index ba14b39..99b0547 100644
--- a/zh/architecutre-concepts/facades.md
+++ b/zh/architecutre-concepts/facades.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
`facades` 为应用的核心功能提供一个「静态」接口,能够提供更加灵活、更加优雅、易于测试的语法。
@@ -39,13 +39,15 @@ func (database *ServiceProvider) Boot() {
| Facade | 文档 |
| -------- | -------------------------------------------------- |
| Artisan | [命令行工具](../digging-deeper/artisan-console.md) |
-| Auth | [用户认证](../digging-deeper/authentication.md) |
-| Gate | [用户授权](../digging-deeper/authorization.md) |
+| Auth | [用户认证](../security/authentication.md) |
+| Gate | [用户授权](../security/authorization.md) |
| Cache | [缓存系统](../digging-deeper/cache.md) |
| Config | [配置信息](../getting-started/configuration.md) |
+| Crypt | [加密解密](../security/encryption.md) |
| Orm | [ORM](../orm/getting-started.md) |
| Event | [事件系统](../digging-deeper/event.md) |
| Grpc | [Grpc](../the-basics/grpc.md) |
+| Hash | [哈希](../security/hashing.md)
| Log | [日志](../the-basics/logging.md) |
| Queue | [队列](../digging-deeper/queues.md) |
| Route | [路由](../the-basics/routing.md) |
diff --git a/zh/architecutre-concepts/request-lifecycle.md b/zh/architecutre-concepts/request-lifecycle.md
index fced033..3bdb54f 100644
--- a/zh/architecutre-concepts/request-lifecycle.md
+++ b/zh/architecutre-concepts/request-lifecycle.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
Goravel 应用的所有请求入口都是 `main.go` 文件,该文件中使用 `bootstrap.Boot()` 引导框架加载。
diff --git a/zh/architecutre-concepts/service-providers.md b/zh/architecutre-concepts/service-providers.md
index 1eaefde..d34b47b 100644
--- a/zh/architecutre-concepts/service-providers.md
+++ b/zh/architecutre-concepts/service-providers.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
内核启动过程中最重要的是加载 `服务提供者` 。应用下所有的服务提供者均被配置到了 `config/app.go` 文件中的 `providers` 数组中。
diff --git a/zh/digging-deeper/artisan-console.md b/zh/digging-deeper/artisan-console.md
index 8b2665e..35d21b6 100644
--- a/zh/digging-deeper/artisan-console.md
+++ b/zh/digging-deeper/artisan-console.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
Artisan 是 Goravel 自带的命令行工具,该模块可以使用 `facades.Artisan` 进行操作。它提供了许多有用的命令,这些命令可以在构建应用时为你提供帮助。你可以通过命令查看所有可用的 Artisan 命令:
diff --git a/zh/digging-deeper/cache.md b/zh/digging-deeper/cache.md
index fa609be..bb8586a 100644
--- a/zh/digging-deeper/cache.md
+++ b/zh/digging-deeper/cache.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
Goravel 提供了可拓展的缓存模块,该模块可以使用 `facades.Cache` 进行操作。
diff --git a/zh/digging-deeper/event.md b/zh/digging-deeper/event.md
index 0296848..04e86ab 100644
--- a/zh/digging-deeper/event.md
+++ b/zh/digging-deeper/event.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
Goravel 的事件系统提供了一个简单的观察者模式的实现,允许你能够订阅和监听在你的应用中的发生的各种事件。事件类一般来说存储在 `app/events` 目录,监听者的类存储在 `app/listeners` 目录。不要担心在你的应用中没有看到这两个目录,因为通过 Artisan 命令行来创建事件和监听者的时候目录会同时被创建。
diff --git a/zh/digging-deeper/filesystem.md b/zh/digging-deeper/filesystem.md
index 3109908..5ecf87f 100644
--- a/zh/digging-deeper/filesystem.md
+++ b/zh/digging-deeper/filesystem.md
@@ -261,21 +261,27 @@ err := facades.Storage.DeleteDirectory(directory)
```go
type Driver interface {
- WithContext(ctx context.Context) Driver
+ AllDirectories(path string) ([]string, error)
+ AllFiles(path string) ([]string, error)
+ Copy(oldFile, newFile string) error
+ Delete(file ...string) error
+ DeleteDirectory(directory string) error
+ Directories(path string) ([]string, error)
+ Exists(file string) bool
+ Files(path string) ([]string, error)
+ Get(file string) (string, error)
+ MakeDirectory(directory string) error
+ Missing(file string) bool
+ Move(oldFile, newFile string) error
+ Path(file string) string
Put(file, content string) error
PutFile(path string, source File) (string, error)
PutFileAs(path string, source File, name string) (string, error)
- Get(file string) (string, error)
Size(file string) (int64, error)
- Path(file string) string
- Exists(file string) bool
- Missing(file string) bool
- Url(file string) string
TemporaryUrl(file string, time time.Time) (string, error)
- Copy(oldFile, newFile string) error
- Move(oldFile, newFile string) error
- Delete(file ...string) error
- MakeDirectory(directory string) error
- DeleteDirectory(directory string) error
+ WithContext(ctx context.Context) Driver
+ Url(file string) string
}
```
+
+> 注意:由于注册驱动时配置信息尚未加载完毕,所以在自定义驱动中,请使用 `facades.Config.Env` 获取配置信息。
diff --git a/zh/digging-deeper/queues.md b/zh/digging-deeper/queues.md
index bc5446f..3ed2c27 100644
--- a/zh/digging-deeper/queues.md
+++ b/zh/digging-deeper/queues.md
@@ -192,6 +192,14 @@ err := facades.Queue.Chain([]queue.Jobs{
}).Dispatch()
```
+### 延迟调度
+
+如果您想指定任务不应立即被队列处理,您可以在调度任务时使用 `Delay` 方法。例如,让我们指定一个任务在分派 10 分钟后处理:
+
+```go
+err := facades.Queue.Job(&jobs.Test{}, []queue.Arg{}).Delay(time.Now().Add(100*time.Second)).Dispatch()
+```
+
### 自定义队列 & 连接
#### 分派到特定队列
diff --git a/zh/digging-deeper/task-scheduling.md b/zh/digging-deeper/task-scheduling.md
index 6ab3e27..014976a 100644
--- a/zh/digging-deeper/task-scheduling.md
+++ b/zh/digging-deeper/task-scheduling.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
过去,你可能需要在服务器上为每一个调度任务去创建 Cron 条目。因为这些任务的调度不是通过代码控制的,你要查看或新增任务调度都需要通过 SSH 远程登录到服务器上去操作,所以这种方式很快会让人变得痛苦不堪。
diff --git a/zh/getting-started/compile.md b/zh/getting-started/compile.md
index cae4da0..1aa3888 100644
--- a/zh/getting-started/compile.md
+++ b/zh/getting-started/compile.md
@@ -33,7 +33,7 @@ CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build .
## Docker
-Goravel 默认自带一个 Dockerfile 文件,可以直接使用。
+Goravel 默认自带 `Dockerfile` 与 `docker-compose.yml` 文件,可以直接使用,注意此时 `APP_HOST` 应为 `0.0.0.0`。
```
docker build .
@@ -70,3 +70,18 @@ COPY --from=builder /build/.env /www/.env
ENTRYPOINT ["/www/main"]
```
+
+### Docker Compose
+
+您也可以使用以下命令快速启动服务:
+
+```
+docker-compose build
+docker-compose up
+```
+
+> 注意:如需外部访问,需要将 APP_HOST 改为 0.0.0.0
+
+## 减小打包体积
+
+将 `config/app.go::providers` 中未用到的 `ServiceProvider` 注释掉将能有效地减少打包体积。
diff --git a/zh/getting-started/configuration.md b/zh/getting-started/configuration.md
index 307f4ff..466c959 100644
--- a/zh/getting-started/configuration.md
+++ b/zh/getting-started/configuration.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
Goravel 框架所有配置文件都保存在 `config` 目录中。你可以进入具体文件查看配置说明,根据项目需要灵活配置。
diff --git a/zh/getting-started/directory-structure.md b/zh/getting-started/directory-structure.md
index eb587c4..ef6e5d8 100644
--- a/zh/getting-started/directory-structure.md
+++ b/zh/getting-started/directory-structure.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
默认的文件结构可以使你更好的开始项目推进,你也可以自由的新增文件夹,但默认文件夹不要修改。
diff --git a/zh/getting-started/installation.md b/zh/getting-started/installation.md
index 912bd03..ceb7b0b 100644
--- a/zh/getting-started/installation.md
+++ b/zh/getting-started/installation.md
@@ -38,7 +38,7 @@ go run . --env=../.env
### 热更新
-框架内置 [cosmtrek/air](https://github.com/cosmtrek/air) 配置文件,可直接使用:
+安装 [cosmtrek/air](https://github.com/cosmtrek/air),框架内置配置文件,可直接使用:
```
air
@@ -68,7 +68,7 @@ go run . artisan key:generate
### 生成 JWT Token
-如果使用到了 [用户认证](../digging-deeper/authentication.md) 功能,需要初始化 JWT Token。
+如果使用到了 [用户认证](../security/authentication.md) 功能,需要初始化 JWT Token。
```
go run . artisan jwt:secret
diff --git a/zh/digging-deeper/authentication.md b/zh/security/authentication.md
similarity index 83%
rename from zh/digging-deeper/authentication.md
rename to zh/security/authentication.md
index 3bfd0a0..c438d50 100644
--- a/zh/digging-deeper/authentication.md
+++ b/zh/security/authentication.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
用户认证是 Web 应用中不可或缺的功能,Goravel 的 `facades.Auth` 模块提供 JWT 功能的支持。
@@ -38,10 +38,19 @@ token, err := facades.Auth.LoginUsingID(ctx, 1)
## 解析 Token
```go
-err := facades.Auth.Parse(ctx, token)
+payload, err := facades.Auth.Parse(ctx, token)
```
-可以通过 err 来判断 Token 是否过期:
+可以通过 `payload` 获取:
+
+1. `Guard`: 当前 Guard;
+2. `Key`: 用户标识;
+3. `ExpireAt`: 过期时间;
+4. `IssuedAt`: 发行时间;
+
+> 当 `err` 为非 `ErrorTokenExpired` 的错误时,payload == nil
+
+可以通过 `err` 来判断 `Token` 是否过期:
```go
"errors"
diff --git a/zh/digging-deeper/authorization.md b/zh/security/authorization.md
similarity index 96%
rename from zh/digging-deeper/authorization.md
rename to zh/security/authorization.md
index 29cee4c..675a759 100644
--- a/zh/digging-deeper/authorization.md
+++ b/zh/security/authorization.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
除了提供内置的 [身份验证(authentication)](./authentication.md) 服务外,Goravel 还提供了一种可以很简单就进行使用的方法,来对用户与资源的授权关系进行管理。即使用户已经通过了「身份验证(authentication)」, 用户也可能无权对应用程序中的模型或数据库记录进行删除或更改。
@@ -35,7 +35,7 @@ func (receiver *AuthServiceProvider) Register() {
}
func (receiver *AuthServiceProvider) Boot() {
- facades.Gate.Define("update-post", func(ctx context.Context, arguments map[string]any) *access.Response {
+ facades.Gate.Define("update-post", func(ctx context.Context, arguments map[string]any) access.Response {
user := ctx.Value("user").(models.User)
post := arguments["post"].(models.Post)
@@ -106,7 +106,7 @@ if (response.Allowed()) {
有时,您可能希望将所有权限授予特定用户。您可以使用 `Before` 方法。该方法将定义该授权拦截规则,优先于所有其他授权拦截规则前执行:
```go
-facades.Gate.Before(func(ctx context.Context, ability string, arguments map[string]any) *access.Response {
+facades.Gate.Before(func(ctx context.Context, ability string, arguments map[string]any) access.Response {
user := ctx.Value("user").(models.User)
if isAdministrator(user) {
return access.NewAllowResponse()
@@ -121,7 +121,7 @@ facades.Gate.Before(func(ctx context.Context, ability string, arguments map[stri
您还可以使用 `After` 方法,来定义在所有授权拦截规则执行后,再次进行授权拦截规则判定:
```go
-facades.Gate.After(func(ctx context.Context, ability string, arguments map[string]any, result *access.Response) *access.Response {
+facades.Gate.After(func(ctx context.Context, ability string, arguments map[string]any, result access.Response) access.Response {
user := ctx.Value("user").(models.User)
if isAdministrator(user) {
return access.NewAllowResponse()
@@ -173,7 +173,7 @@ func NewPostPolicy() *PostPolicy {
return &PostPolicy{}
}
-func (r *PostPolicy) Update(ctx context.Context, arguments map[string]any) *access.Response {
+func (r *PostPolicy) Update(ctx context.Context, arguments map[string]any) access.Response {
user := ctx.Value("user").(models.User)
post := arguments["post"].(models.Post)
diff --git a/zh/security/encryption.md b/zh/security/encryption.md
new file mode 100644
index 0000000..4648eea
--- /dev/null
+++ b/zh/security/encryption.md
@@ -0,0 +1,29 @@
+# 加密解密
+
+[[toc]]
+
+## 简介
+
+Goravel 的加密机制使用的是 OpenSSL 所提供的 AES-256 加密。强烈建议你使用 Goravel 内建的加密工具,而不是用其它的加密算法。所有 Goravel 加密之后的结果都会使用消息认证码 (MAC) 签名,使其底层值不能在加密后再次修改。
+
+## 配置
+
+在使用 Goravel 的加密工具之前,你必须先设置 `config/app.go` 配置文件中的 `key` 配置项。该配置项由环境变量 `APP_KEY` 设定。你应当使用 `go run . artisan key:generate` 命令来生成该变量的值,`key:generate` 命令将使用 Golang 的安全随机字节生成器为你的应用程序构建加密安全密钥。
+
+## 基本用法
+
+### 加密一个值
+
+你可以使用 `facades.Crypt` 提供的 `EncryptString` 来加密一个值。所有加密的值都使用 OpenSSL 的 `AES-256-CBC` 来进行加密。此外,所有加密过的值都会使用消息认证码 (MAC) 来签名,以检测加密字符串是否被篡改过:
+
+```go
+secret, err := facades.Crypt.EncryptString("goravel")
+```
+
+### 解密一个值
+
+您可以使用 `facades.Crypt` 提供的 `DecryptString` 来进行解密。如果该值不能被正确解密,例如消息认证码 (MAC) 无效,会返回错误:
+
+```go
+str, err := facades.Crypt.DecryptString(secret)
+```
diff --git a/zh/security/hashing.md b/zh/security/hashing.md
new file mode 100644
index 0000000..feed0b2
--- /dev/null
+++ b/zh/security/hashing.md
@@ -0,0 +1,41 @@
+# 哈希
+
+[[toc]]
+
+## 简介
+
+Goravel `facades.Hash` 为存储用户密码提供了安全的 Argon2id 和 Bcrypt 哈希加密方式。如果你正在使用 Goravel 应用初始脚手架 ,默认情况下,将使用 Argon2id 进行注册和身份验证。
+
+## 配置
+
+你可以在 `config/hashing.go` 配置文件中配置默认哈希驱动程序。目前支持两种驱动程序: Bcrypt 和 Argon2id。
+
+## 基本用法
+
+### 哈希密码
+
+你可以通过调用 `facades.Hash` 的 `Make` 方法来加密你的密码:
+
+```go
+password, err := facades.Hash.Make(password)
+```
+
+### 验证密码是否与哈希匹配
+
+`Check` 方法能为你验证一段给定的未加密字符串与给定的散列 / 哈希值是否一致:
+
+```go
+if facades.Hash.Check('plain-text', hashedPassword) {
+ // 密码匹配...
+}
+```
+
+### 检查密码是否需要重新散列 / 哈希
+
+`NeedsRehash` 方法可以为你检查当散列 / 哈希的加密系数改变时,你的密码是否被新的加密系数重新加密过。某些应用程序选择在身份验证时执行这一项检查:
+
+```go
+if facades.Hash.NeedsRehash(hashed) {
+ hashed = facades.Hash.Make('plain-text');
+}
+```
diff --git a/zh/the-basics/controllers.md b/zh/the-basics/controllers.md
index d48870d..084fc4a 100644
--- a/zh/the-basics/controllers.md
+++ b/zh/the-basics/controllers.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
为了代替在单独路由中以闭包形式定义所有的请求处理逻辑,可以使用控制器来进行整合。控制器被存放在 `app/http/controllers` 目录中。
diff --git a/zh/the-basics/grpc.md b/zh/the-basics/grpc.md
index 51ee590..f1d34ce 100644
--- a/zh/the-basics/grpc.md
+++ b/zh/the-basics/grpc.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
Grpc 模块可以使用 `facades.Grpc` 进行操作。
@@ -160,7 +160,8 @@ func init() {
// Interceptors can be the group name of UnaryClientInterceptorGroups in app/grpc/kernel.go.
"clients": map[string]any{
"user": map[string]any{
- "host": config.Env("GRPC_HOST", ""),
+ "host": config.Env("GRPC_USER_HOST", ""),
+ "port": config.Env("GRPC_USER_PORT", ""),
"interceptors": []string{"trace"},
},
},
diff --git a/zh/the-basics/logging.md b/zh/the-basics/logging.md
index da0155c..b45208d 100644
--- a/zh/the-basics/logging.md
+++ b/zh/the-basics/logging.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
为了了解应用程序的运行状况,Goravel 提供了强大的日志模块,可以通过 `facades.Log` 将日志消息、系统错误记录到文件或其他通道。
@@ -12,12 +12,14 @@
`Goravel` 默认使用 `stack` 通道记录日志,`stack` 允许日志转发到多个通道中。
+`single` 和 `daily` 驱动中的 `print` 配置可以控制日志输出到控制台。
+
## 可用的通道驱动
| 名称 | 描述 |
| -------- | ---------------- |
| `stack` | 允许使用多个通道 |
-| `single` | 单日志文件 |
+| `single` | 单日志文件 |
| `daily` | 每天一个日志文件 |
| `custom` | 自定义驱动 |
diff --git a/zh/the-basics/middleware.md b/zh/the-basics/middleware.md
index aa65923..03545f9 100644
--- a/zh/the-basics/middleware.md
+++ b/zh/the-basics/middleware.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
中间件可以过滤进入应用程序的 HTTP 请求。例如 `Goravel` 提供一个 CORS 中间件,可以实现请求跨域。
@@ -46,6 +46,7 @@ Goravel 中自带了一些中间件可供使用:
| 中间件 | 作用 |
| ------------------------------------------------- | -------- |
| github.com/goravel/framework/http/middleware/Cors | 实现跨域 |
+| github.com/goravel/framework/http/middleware/Throttle | 限流器 |
### 命令创建中间件
```
diff --git a/zh/the-basics/request.md b/zh/the-basics/request.md
index 5973004..0e534f2 100644
--- a/zh/the-basics/request.md
+++ b/zh/the-basics/request.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
Goravel 的 `contracts/http/Request` 方法可以与应用程序处理的当前 HTTP 请求进行交互,以及检索与请求一起提交的输入内容和文件。
@@ -57,18 +57,26 @@ method := ctx.Request().Ip()
## 输入
-### 获取链接中的参数
+### 获取路由中的参数
```go
// /users/{id}
-id := ctx.Request().Input("id")
+id := ctx.Request().Route("id")
+id := ctx.Request().RouteInt("id")
+id := ctx.Request().RouteInt64("id")
```
-### 获取链接传入的参数
+### 获取路由传入的参数
```go
// /users?name=goravel
-name := ctx.Request().Query("name", "goravel")
+name := ctx.Request().Query("name")
+name := ctx.Request().Query("name", "default")
+
+// /users?id=1
+name := ctx.Request().QueryInt("id")
+name := ctx.Request().QueryInt64("id")
+name := ctx.Request().QueryBool("id")
// /users?names=goravel1&names=goravel2
names := ctx.Request().QueryArray("names")
@@ -80,7 +88,29 @@ names := ctx.Request().QueryMap("names")
### 获取 form
```go
-name := ctx.Request().Form("name", "goravel")
+name := ctx.Request().Form("name")
+name := ctx.Request().Form("name", "default")
+```
+
+### 获取 json
+
+```go
+name := ctx.Request().Json("name")
+name := ctx.Request().Json("name", "goravel")
+```
+
+> 注意:只能获取一维 Json 数据,否则将返回空。
+
+### 检索一个输入值
+
+获取所有的用户输入数据,而不用在意用户使用的是哪种 HTTP 动词,不管是什么 HTTP 动词。检索顺序为:`json`, `form`, `query`, `route`。
+
+```go
+name := ctx.Request().Input("name")
+name := ctx.Request().Json("name", "goravel")
+name := ctx.Request().InputInt("name")
+name := ctx.Request().InputInt64("name")
+name := ctx.Request().InputBool("name")
```
### json/form 绑定 struct
@@ -94,6 +124,11 @@ var user User
err := ctx.Request().Bind(&user)
```
+```go
+var user map[string]any
+err := ctx.Request().Bind(&user)
+```
+
## 文件
### 获取上传的文件
diff --git a/zh/the-basics/response.md b/zh/the-basics/response.md
index 4ea3a57..b344040 100644
--- a/zh/the-basics/response.md
+++ b/zh/the-basics/response.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
可以使用 `ctx.Response()` 在控制其中进行 HTTP 响应。
diff --git a/zh/the-basics/routing.md b/zh/the-basics/routing.md
index 2c70ff5..3b50fe0 100644
--- a/zh/the-basics/routing.md
+++ b/zh/the-basics/routing.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
Goravel 路由模块可以使用 `facades.Route` 进行操作。
@@ -14,7 +14,7 @@ Goravel 路由模块可以使用 `facades.Route` 进行操作。
## 启动 HTTP 服务器
-在根目录下 `main.go` 中启动 HTTP 服务器
+在根目录下 `main.go` 中启动 HTTP 服务器,`facades.Route.Run()` 将会自动获取 `route.host` 的配置。
```go
package main
@@ -31,7 +31,7 @@ func main() {
//Start http server by facades.Route.
go func() {
- if err := facades.Route.Run(facades.Config.GetString("app.host")); err != nil {
+ if err := facades.Route.Run(); err != nil {
facades.Log.Errorf("Route run error: %v", err)
}
}()
@@ -51,17 +51,28 @@ func main() {
import "github.com/goravel/framework/http/middleware"
func (kernel *Kernel) Middleware() []http.Middleware {
- return []http.Middleware{
- middleware.Tls(facades.Config.GetString("app.host")),
- }
+ return []http.Middleware{
+ middleware.Tls(),
+ }
}
```
### 启动服务器
+`facades.Route.RunTLS()` 将会自动获取 `route.tls` 的配置:
+
```go
// main.go
-if err := facades.Route.RunTLS(facades.Config.GetString("app.host"), "ca.pem", "ca.key"); err != nil {
+if err := facades.Route.RunTLS(); err != nil {
+ facades.Log.Errorf("Route run error: %v", err)
+}
+```
+
+您也可以使用 `facades.Route.RunTLSWithCert()` 方法,自定义 host 与 证书:
+
+```go
+// main.go
+if err := facades.Route.RunTLSWithCert("127.0.0.1:3000", "ca.pem", "ca.key"); err != nil {
facades.Log.Errorf("Route run error: %v", err)
}
```
@@ -71,6 +82,8 @@ if err := facades.Route.RunTLS(facades.Config.GetString("app.host"), "ca.pem", "
| 方法 | 作用 |
| ---------- | ------------------------------------- |
| Run | [启动 HTTP 服务器](#启动-HTTP-服务器) |
+| RunTLS | [启动 HTTPS 服务器](#启动-HTTPS-服务器) |
+| RunTLSWithCert | [启动 HTTPS 服务器](#启动-HTTPS-服务器) |
| Group | [路由分组](#路由分组) |
| Prefix | [路由前缀](#路由前缀) |
| ServeHTTP | [测试路由](#测试路由) |
@@ -128,6 +141,31 @@ facades.Route.StaticFile("static-file", "./public/logo.png")
facades.Route.StaticFS("static-fs", http.Dir("./public"))
```
+一般情况下,我们无法将文件路由定向到根目录 `/`,如果您真的想这么做,可以使用如下方式:
+
+```go
+// 安装依赖
+go get -u github.com/gin-contrib/static
+
+// 定义中间件 app/http/middleware/static.go,然后将其注册到 app/http/kernel.go
+package middleware
+
+import (
+ "github.com/gin-contrib/static"
+
+ contractshttp "github.com/goravel/framework/contracts/http"
+ frameworkhttp "github.com/goravel/framework/http"
+)
+
+func Static() contractshttp.Middleware {
+ return func(ctx contractshttp.Context) {
+ static.Serve("/", static.LocalFile("./public", false))(ctx.(*frameworkhttp.GinContext).Instance())
+
+ ctx.Request().Next()
+ }
+}
+```
+
## 路由传参
```go
@@ -150,6 +188,103 @@ facades.Route.Middleware(middleware.Cors()).Get("users", userController.Show)
详见[中间件](./middleware.md)
+## 速率限制
+
+### 定义速率限制器
+
+Goravel 包含强大且可自定义的速率限制服务,你可以利用这些服务来限制给定路由或一组路由的流量。首先,你应该定义满足应用程序需求的速率限制器配置。通常,这应该在应用程序的 `app/providers/route_service_provider.go` 文件的 `configureRateLimiting` 方法中完成。
+
+速率限制器使用 `facades.RateLimiter` 的 `For` 方法进行定义。`For` 方法接受一个速率限制器名称和一个闭包,该闭包返回应该应用于分配给速率限制器的路由的限制配置。速率限制器名称可以是你希望的任何字符串:
+
+```go
+import (
+ contractshttp "github.com/goravel/framework/contracts/http"
+ "github.com/goravel/framework/facades"
+ "github.com/goravel/framework/http/limit"
+)
+
+func (receiver *RouteServiceProvider) configureRateLimiting() {
+ facades.RateLimiter.For("global", func(ctx contractshttp.Context) contractshttp.Limit {
+ return limit.PerMinute(1000)
+ })
+}
+```
+
+如果传入的请求超过指定的速率限制,Goravel 将自动返回一个带有 429 HTTP 状态码的响应。如果你想定义自己的响应,应该由速率限制返回,你可以使用 `Response` 方法:
+
+```go
+facades.RateLimiter.For("global", func(ctx contractshttp.Context) contractshttp.Limit {
+ return limit.PerMinute(1000).Response(func(ctx contractshttp.Context) {
+ ctx.Response().String(429, "Custom response...")
+ return
+ })
+})
+```
+
+由于速率限制器回调接收传入的 HTTP 请求实例,你可以根据传入的请求或经过身份验证的用户动态构建适当的速率限制:
+
+```go
+facades.RateLimiter.For("global", func(ctx contractshttp.Context) contractshttp.Limit {
+ // 假设
+ if is_vip() {
+ return limit.PerMinute(100)
+ }
+
+ return nil
+})
+```
+
+#### 分段速率限制
+
+有时你可能希望按某个任意值对速率限制进行分段。例如,你可能希望每个 IP 地址每分钟允许用户访问给定路由 100 次。为此,你可以在构建速率限制时使用 `By` 方法:
+
+```go
+facades.RateLimiter.For("global", func(ctx contractshttp.Context) contractshttp.Limit {
+ if is_vip() {
+ return limit.PerMinute(100).By(ctx.Request().Ip())
+ }
+
+ return nil
+})
+```
+
+为了使用另一个示例来说明此功能,我们可以将每个经过身份验证的用户 ID 的路由访问限制为每分钟 100 次,或者对于访客来说,每个 IP 地址每分钟访问 10 次:
+
+```go
+facades.RateLimiter.For("global", func(ctx contractshttp.Context) contractshttp.Limit {
+ if userID != 0 {
+ return limit.PerMinute(100).By(userID)
+ }
+
+ return limit.PerMinute(100).By(ctx.Request().Ip())
+})
+```
+
+#### 多个速率限制
+
+如果需要,你可以返回给定速率限制器配置的速率限制数组。将根据路由在数组中的放置顺序评估每个速率限制:
+
+```go
+facades.RateLimiter.ForWithLimits("login", func(ctx contractshttp.Context) []contractshttp.Limit {
+ return []contractshttp.Limit{
+ limit.PerMinute(500),
+ limit.PerMinute(100).By(ctx.Request().Ip()),
+ }
+})
+```
+
+### 将速率限制器附加到路由
+
+可以使用 `Throttle` middleware 将速率限制器附加到路由或路由组。路由中间件接受你希望分配给路由的速率限制器的名称:
+
+```go
+facades.Route.Middleware(middleware.Throttle("global")).Get("/", func(ctx http.Context) {
+ ctx.Response().Json(200, http.Json{
+ "Hello": "Goravel",
+ })
+})
+```
+
## 跨域资源共享 (CORS)
Goravel 已默认启用 CORS,详细配置可以到 `config/cors.go` 文件中进行修改,该功能被作为全局中间件注册在 `app/http/kernel.go` 中。
diff --git a/zh/the-basics/validation.md b/zh/the-basics/validation.md
index c86722d..7d747b2 100644
--- a/zh/the-basics/validation.md
+++ b/zh/the-basics/validation.md
@@ -2,7 +2,7 @@
[[toc]]
-## 介绍
+## 简介
Goravel 提供了几种不同的方法来验证传入应用程序的数据。最常见的做法是在所有传入的 HTTP 请求中使用 `validate` 方法。Goravel 包含了各种方便的验证规则。
@@ -94,12 +94,39 @@ go run . artisan make:request StorePostRequest
正如您可能已经猜到的那样,`Authorize` 方法负责确定当前经过身份验证的用户是否可以执行请求操作,而 `Rules` 方法则返回适用于请求数据的验证规则:
```go
-func (r *StorePostRequest) Rules() map[string]string {
+package requests
+
+import (
+ "github.com/goravel/framework/contracts/http"
+ "github.com/goravel/framework/contracts/validation"
+)
+
+type StorePostRequest struct {
+ Name string `form:"name" json:"name"`
+}
+
+func (r *StorePostRequest) Authorize(ctx http.Context) error {
+ return nil
+}
+
+func (r *StorePostRequest) Rules(ctx http.Context) map[string]string {
return map[string]string{
- "title": "required|max_len:255",
- "body": "required",
+ // 键与传入的键保持一致
+ "name": "required|max_len:255",
}
}
+
+func (r *StorePostRequest) Messages(ctx http.Context) map[string]string {
+ return map[string]string{}
+}
+
+func (r *StorePostRequest) Attributes(ctx http.Context) map[string]string {
+ return map[string]string{}
+}
+
+func (r *StorePostRequest) PrepareForValidation(ctx http.Context, data validation.Data) error {
+ return nil
+}
```
所以,验证规则是如何运行的呢?您所需要做的就是在控制器方法中类型提示传入的请求。在调用控制器方法之前验证传入的表单请求,这意味着您不需要在控制器中写任何验证逻辑:
@@ -111,9 +138,11 @@ func (r *PostController) Store(ctx http.Context) {
}
```
+> 注意,由于 `form` 传值默认为 `string` 类型,因此 request 中所有字段也都应为 `string` 类型,否则请使用 `JSON` 传值。
+
### 表单请求授权验证
-表单请求类内也包含了 `Authorize` 方法。在这个方法中,您可以检查经过身份验证的用户确定其是否具有更新给定资源的权限。例如,您可以判断用户是否拥有更新文章评论的权限。最有可能的是,您将通过以下方法与您的 [授权与策略](../digging-deeper/authorization.md) 进行交互:
+表单请求类内也包含了 `Authorize` 方法。在这个方法中,您可以检查经过身份验证的用户确定其是否具有更新给定资源的权限。例如,您可以判断用户是否拥有更新文章评论的权限。最有可能的是,您将通过以下方法与您的 [授权与策略](../security/authorization.md) 进行交互:
```go
func (r *StorePostRequest) Authorize(ctx http.Context) error {
diff --git a/zh/upgrade/v1.10.md b/zh/upgrade/v1.10.md
new file mode 100644
index 0000000..93013f8
--- /dev/null
+++ b/zh/upgrade/v1.10.md
@@ -0,0 +1,227 @@
+# 从 v1.9 升级到 v1.10
+
+[[toc]]
+
+## 令人兴奋的新功能 🎉
+
+- [新增 facades.Crypt(1.10.0)](#加密解密)
+- [新增 facades.Hash(1.10.0)](#哈希)
+- [新增路由限流器(1.10.0)](#新增路由限流器)
+
+## 功能增强 🚀
+
+- [优化 HTTP 启动方式(1.10.0)](#优化-HTTP-启动方式)
+- [优化 GPRC 启动方式(1.10.0)](#优化-GRPC-启动方式)
+- [增加控制日志输出到控制台的配置(1.10.0)](#增加控制日志输出到控制台的配置)
+- [Request 修改、新增方法(1.10.0)](#Request-修改、新增方法)
+- [队列支持延迟调度(1.10.0)](#队列支持延迟调度)
+- [ORM Connection 支持配置表名前缀与单复数(1.10.0)](#ORM-Connection-支持配置表名前缀与单复数)
+- [新增 docker-compose.yml 文件(1.10.0)](#新增-docker-compose.yml-文件)
+- [优化 Orm(1.10.0)](#优化-Orm)
+- [迁移文件中支持执行多条 SQL 语句](#迁移文件中支持执行多条-SQL-语句)
+- [文件系统新增 minio 驱动](#文件系统新增-minio-驱动)
+- [contracts/http 增加 net/http 的 Status 映射](#contracts/http-增加-net/http-的-Status-映射)
+
+## 破坏性变化 🛠
+
+- [APP_KEY 必填(1.10.0)](#APP_KEY-必填)
+- [表单验证中方法新增 ctx 参数(1.10.0)](#表单验证中方法新增-ctx-参数)
+- [facades.Auth.Parse 新增 payload 返回](#facades.Auth.Parse-新增-payload-返回)
+- [Orm 的部分方法新增返回值](#Orm-的部分方法新增返回值)
+
+## 升级指南
+
+**预计升级时间:20 分钟**
+
+### 更新依赖
+
+`go.mod` 中更新依赖:
+
+```
+go get -u github.com/goravel/framework@v1.10.0
+```
+
+### 加密解密
+
+Version: v1.10.0
+
+新增 `facades.Crypt`:
+
+1. [config/app.go](https://github.com/goravel/goravel/blob/v1.10.x/config/app.go) 文件 `providers` 新增 `&crypt.ServiceProvider{},`。
+
+[查看文档](../security/encryption.md)
+
+### 哈希
+
+Version: v1.10.0
+
+新增 `facades.Hash`:
+
+1. [config/app.go](https://github.com/goravel/goravel/blob/v1.10.x/config/app.go) 文件 `providers` 新增 `&hash.ServiceProvider{},`;
+
+2. 新增 [config/hashing.go](https://github.com/goravel/goravel/blob/v1.10.x/config/hashing.go) 文件;
+
+[查看文档](../security/hashing.md)
+
+### 新增路由限流器
+
+Version: v1.10.0
+
+[查看文档](../the-basics/routing.md#速率限制)
+
+### 优化 HTTP 启动方式
+
+Version: v1.10.0
+
+1. 新增 `config/http.go` 配置文件,[详见文件](https://github.com/goravel/goravel/blob/v1.10.x/config/http.go);
+2. `facades.Route.Run` 方法不再需要传参,默认读取 `http.host` 和 `http.port`(您无需修改代码,向下兼容);
+3. `facades.Route.RunTLS` 方法不再需要传参,默认读取 `http.tls.host`,`http.tls.port`,`http.tls.ssl.cert` 和 `http.tls.ssl.key`,如果用到,需修改代码;
+4. 新增 `facades.Route.RunTLSWithCert` 方法,[详见文档](../the-basics/routing.md#启动服务器);
+5. 移动配置 `app.url`, `app.host` 到 `http.url`, `http.host`;
+
+### 优化 GRPC 启动方式
+
+Version: v1.10.0
+
+`facades.Grpc.Run` 方法不再需要传参,默认读取 `grpc.host` 和 `grpc.port`(您无需修改代码,向下兼容);
+
+### 增加控制日志输出到控制台的配置
+
+Version: v1.10.0
+
+`config/logging.go` 文件中,`single`, `daily` channel 新增 `print` 配置,可以控制日志是否输出到控制台,[详见文件](https://github.com/goravel/goravel/blob/v1.10.x/config/logging.go);
+
+### Request 修改、新增方法
+
+Version: v1.10.0
+
+1. `Input` 方法由仅获取路由参数,修改为根据以下顺序获取数据:`json`, `form`, `query`, `route`。注意:`json` 只能获取一维数据,否则将返回空;
+2. 新增 `Route` 方法替代原有 `Input` 方法功能;
+3. `Query` 与 `Form` 方法默认值修改为不必填;
+4. 新增以下方法:
+
+| 方法名 | 作用 |
+| ----------- | -------------- |
+| Route | [获取路由中的参数](../the-basics/request.md#获取路由中的参数) |
+| RouteInt | [获取路由中的参数](../the-basics/request.md#获取路由中的参数) |
+| RouteInt64 | [获取路由中的参数](../the-basics/request.md#获取路由中的参数) |
+| QueryInt | [获取路由传入的参数](../the-basics/request.md#获取路由传入的参数) |
+| QueryInt64 | [获取路由传入的参数](../the-basics/request.md#获取路由传入的参数) |
+| QueryBool | [获取路由传入的参数](../the-basics/request.md#获取路由传入的参数) |
+| InputInt | [获取路由传入的参数](../the-basics/request.md#获取路由传入的参数) |
+| InputInt64 | [获取路由传入的参数](../the-basics/request.md#获取路由传入的参数) |
+| InputBool | [获取路由传入的参数](../the-basics/request.md#获取路由传入的参数) |
+| Json | [获取路由传入的 Json](../the-basics/request.md#获取-json) |
+
+### 队列支持延迟调度
+
+Version: v1.10.0
+
+新增 `Delay` 方法,[详见文档](../digging-deeper/queues.md#延迟调度)
+
+### ORM Connection 支持配置表名前缀与单复数
+
+Version: v1.10.0
+
+1. `Model` 支持指定表名,[详见文档](../orm/getting-started.md#指定表名);
+2. `config/database.go` 中的 `connection` 新增键值:
+
+`prefix`:设置表名前缀;
+`singular`:设置表名使用单数还是复数;
+
+[详见文件](https://github.com/goravel/goravel/blob/v1.10.x/config/database.go)
+
+### 新增 docker-compose.yml 文件
+
+Version: v1.10.0
+
+您现在可以使用以下命令快速启动服务:
+
+```
+docker-compose build
+docker-compose up
+```
+
+### 优化 Orm
+
+Version: v1.10.0
+
+1. 新增以下方法:
+
+| 方法名 | 作用 |
+| ------------- | --------------------------------------- |
+| FirstOr | [查询或通过回调返回一条数据](../orm/getting-started.md#查询一条数据) |
+| FirstOrCreate | [查询或创建模型](../orm/getting-started.md#查询或创建模型) |
+| FirstOrNew | [查询或实例化模型](../orm/getting-started.md#查询或创建模型) |
+| FirstOrFail | [未找到时抛出错误](../orm/getting-started.md#未找到时抛出错误) |
+| UpdateOrCreate | [更新或创建一条数据](../orm/getting-started.md#更新或创建一条数据)
+
+2. 之前这样写报错,现在支持:
+
+```go
+query := facades.Orm.Query()
+query = query.Where()
+```
+
+### 迁移文件中支持执行多条 SQL 语句
+
+Version: v1.10.0
+
+之前迁移文件中仅支持执行一条 SQL 语句,现在支持多条。
+
+### 文件系统新增 minio 驱动
+
+Version: v1.10.0
+
+新增 minio 配置,[详见文件](https://github.com/goravel/goravel/blob/v1.10.x/config/filesystems.go)。
+
+### contracts/http 增加 net/http 的 Status 映射
+
+Version: v1.10.0
+
+可以在 controller 中直接使用 `http.StatusOK` 等状态码,而不需要再导入 `net/http`。
+
+[详见文件](https://github.com/goravel/framework/blob/v1.10.0/contracts/http/status.go)
+
+### APP_KEY 必填
+
+Version: v1.10.0
+
+`.env` 文件中 `APP_KEY` 修改为必填项,可以通过 `go run . artisan key:generate` 生成。
+
+### 表单验证中方法新增 ctx 参数
+
+Version: v1.10.0
+
+表单验证的 `Rules`, `Messages`, `Attributes`, `PrepareForValidation` 方法,新增 `ctx http.Context` 传参,使您可以进行更加自定义的配置。
+
+[详见文档](../the-basics/validation.md#创建表单请求验证)
+
+### facades.Auth.Parse 新增 payload 返回
+
+Version: v1.10.0
+
+`err := facades.Auth.Parse(ctx, token)` 修改为 `payload, err := facades.Auth.Parse(ctx, token)`,通过 `payload` 您可以获取到:
+
+1. `Guard`: 当前 Guard;
+2. `Key`: 用户标识;
+3. `ExpireAt`: 过期时间;
+4. `IssuedAt`: 发行时间;
+
+[详见文档](../security/authentication.md#解析-token)
+
+### Orm 的部分方法新增返回值
+
+Version: v1.10.0
+
+以下方法新增 `*Result` 返回值,以获取影响行数:
+
+```go
+res, err := query.Delete(&user)
+res, err := query.Exec(fmt.Sprintf("DELETE FROM users where id = %d", user.ID))
+res, err := query.ForceDelete(&User{})
+res, err := query.Updates(User{Avatar: "avatar"})
+
+// 获取受影响行数
+num := res.RowsAffected
+```
diff --git a/zh/upgrade/v1.6.md b/zh/upgrade/v1.6.md
index fa8dc6e..319f27c 100644
--- a/zh/upgrade/v1.6.md
+++ b/zh/upgrade/v1.6.md
@@ -27,4 +27,4 @@ github.com/goravel/framework v1.6.3
1. 新增 [app/providers/auth_service_provider.go](https://github.com/goravel/goravel/blob/v1.6.0/app/providers/auth_service_provider.go) 文件;
2. [config/app.go](https://github.com/goravel/goravel/blob/v1.6.0/config/app.go) 文件 `providers` 新增 `&providers.AuthServiceProvider{}`;
-[查看文档](../digging-deeper/authorization.md)
+[查看文档](../security/authorization.md)
diff --git a/zh/upgrade/v1.8.md b/zh/upgrade/v1.8.md
index f572037..654c8d1 100644
--- a/zh/upgrade/v1.8.md
+++ b/zh/upgrade/v1.8.md
@@ -45,12 +45,12 @@ Orm 新增方法,以处理模型关联等操作:
| 方法名 | 作用 |
| ----------- | --------------------------------- |
-| Association | [关联操作](../ORM/association.md#关联操作) |
-| DB | [获取通用数据库接口](../ORM/getting-started.md#获取通用数据库接口) |
-| Load | [延迟预加载](../ORM/association.md#延迟预加载) |
-| LoadMissing | [延迟预加载(不存在)](../ORM/association.md#延迟预加载) |
-| Omit | [忽略关联](../ORM/association.md#创建/更新关联) |
-| With | [预加载](../ORM/association.md#预加载) |
+| Association | [关联操作](../orm/association.md#关联操作) |
+| DB | [获取通用数据库接口](../orm/getting-started.md#获取通用数据库接口) |
+| Load | [延迟预加载](../orm/association.md#延迟预加载) |
+| LoadMissing | [延迟预加载(不存在)](../orm/association.md#延迟预加载) |
+| Omit | [忽略关联](../orm/association.md#创建/更新关联) |
+| With | [预加载](../orm/association.md#预加载) |
### Request 新增方法
@@ -81,9 +81,9 @@ Version: v1.8.0
package bootstrap
import (
- "github.com/goravel/framework/foundation"
+ "github.com/goravel/framework/foundation"
- "goravel/config"
+ "goravel/config"
)
```
diff --git a/zh/upgrade/v1.9.md b/zh/upgrade/v1.9.md
index ecbf17a..0b2a52a 100644
--- a/zh/upgrade/v1.9.md
+++ b/zh/upgrade/v1.9.md
@@ -20,6 +20,7 @@
- [File 获取错误的文件类型(1.9.0)](#File-获取错误的文件类型)
- [修复 make:event 和 make:listener 命令生成模板有误的问题(1.9.0)](#修复-make:event-和-make:listener-命令生成模板有误的问题)
+- [修复保存文件时有些类型无法获取后缀的问题(1.9.1)](#修复保存文件时有些类型无法获取后缀的问题)
## 升级依赖 ⬆️
@@ -41,7 +42,7 @@ go get -u github.com/goravel/framework@v1.9.0
Version: v1.9.0
-数据库支持读写分离配置,[详见文档](../ORM/getting-started.md#读写分离)。
+数据库支持读写分离配置,[详见文档](../orm/getting-started.md#读写分离)。
### 新增数据库连接池配置
@@ -82,7 +83,7 @@ Version: v1.9.0
Version: v1.9.0
-`facades.Orm` 新增 `Paginate` 方法,[详见文档](../ORM/getting-started.md#分页)。
+`facades.Orm` 新增 `Paginate` 方法,[详见文档](../orm/getting-started.md#分页)。
### 新增 make 命令
@@ -101,6 +102,8 @@ go run . artisan make:model User
### Response 新增方法
+Version: v1.9.0
+
| 方法名 | 作用 |
| ----------- | -------------- |
| Data | [自定义返回](../the-basics/response.md#自定义返回) |
@@ -120,4 +123,17 @@ Version: v1.9.0
### 修复 make:event 和 make:listener 命令生成模板有误的问题
+Version: v1.9.0
+
`import "github.com/goravel/framework/contracts/events"` 改为 `import "github.com/goravel/framework/contracts/event"`
+
+### 修复保存文件时有些类型无法获取后缀的问题
+
+Version: v1.9.1
+
+下面代码有些类型无法获取后缀:
+
+```go
+file, err := ctx.Request().File()
+file.Store("upload")
+```