diff --git a/docs/en-US/v2.3.x/README.md b/docs/en-US/v2.3.x/README.md new file mode 100644 index 0000000..85d97b9 --- /dev/null +++ b/docs/en-US/v2.3.x/README.md @@ -0,0 +1,129 @@ +--- +title: Quickly Start +lang: en-US +--- + +# Quickly Start + +First make sure you have GO installed, version 1.16 or higher, and that you have set the GOPATH environment variable and added GOPATH/bin to the environment variable. + +> We recommend that you simply use the latest stable version, as we try to stay on top of the latest version of Go. + +Or you can refer: + +[Windows installation](environment/install_go_windows.md) + +[Linux installation](environment/install_go_linux.md) + +[Mac installation](environment/install_go_mac.md) + +**Please note that after `Beego` V2 we require the `go mod` feature, make sure that the `go mod` feature is turned on, i.e. `GO111MODULE=on`**. More details refer to [Go module](environment/go_mod.md) + +Or you can specify the GOPROXY: + +```shell +GOPROXY=https://goproxy.cn +``` + +Next, let's try to start a `hello world` example. In this example, we will use the `Bee` tool to create the `hello world` project. + +More details refer to [Bee](./bee/README.md) + +## Steps + +If you already have a development environment installed, then you may consider using our quick install script. + +### Mac or Linux + +Run: + +```shell +bash <(curl -s https://raw.githubusercontent.com/beego/beego-doc/main/scripts/quickstart.sh) +``` + +Or using wget: + +```shell +bash <(wget -qO- https://raw.githubusercontent.com/beego/beego-doc/main/scripts/quickstart.sh) +``` + +### Windows + +Using `curl`: + +```shell +bash <(curl -s https://raw.githubusercontent.com/beego/beego-doc/main/scripts/quickstart.bat) +``` + +Or `wget` + +```shell +bash <(wget -qO- https://raw.githubusercontent.com/beego/beego-doc/main/scripts/quickstart.bat) +``` + +## Manual Installation + +In this section, we will use the `go get` command, so if you are not familiar with it, we suggest that you read [Go get](environment/go_get_command.md) + +Always remember, if you experience network problems, or timeout issues, make sure you set up the `GOPROXY` proxy. + +### Install Bee + +Run: + +```shell +go get -u github.com/beego/bee/v2@latest +``` + +And then + +```shell +bee version +``` + +you can see: + +```shell +| ___ \ +| |_/ / ___ ___ +| ___ \ / _ \ / _ \ +| |_/ /| __/| __/ +\____/ \___| \___| v2.0.x + +├── Beego : Beego is not installed. Please do consider installing it first: https://github.com/beego/beego/v2. If you are using go mod, and you don't install the beego under $GOPATH/src/github.com/beego, just ignore this. +├── GoVersion : go1.16 +├── GOOS : linux +├── GOARCH : amd64 +├── NumCPU : 12 +├── GOPATH : /home/xxx/go +├── GOROOT : /home/aaa/bbb/go +├── Compiler : gc +└── Published : 2020-12-16 + +``` + +### Create Project + +Run: + +```shell +bee new hello +cd hello +go mod tidy +``` + +And then: + +```shell +bee run +``` +you can see: + +```shell +2021/03/31 23:29:19 SUCCESS ▶ 0004 Built Successfully! +2021/03/31 23:29:19 INFO ▶ 0005 Restarting 'hello'... +2021/03/31 23:29:19 SUCCESS ▶ 0006 './hello' is running... +2021/03/31 23:29:22.016 [I] [parser.go:413] generate router from comments + +2021/03/31 23:29:22.016 [I] [server.go:241] http server Running on http://:8080 +``` diff --git a/docs/en-US/v2.3.x/bee/README.md b/docs/en-US/v2.3.x/bee/README.md new file mode 100644 index 0000000..1c507d2 --- /dev/null +++ b/docs/en-US/v2.3.x/bee/README.md @@ -0,0 +1,300 @@ +# Introduction to bee tool + +Bee tool is a project for rapid Beego development. With bee tool developers can create, auto compile and reload, develop, test, and deploy Beego applications quickly and easily. + +## Installing bee tool + +Install bee tool with the following command: + +`go get github.com/beego/bee/v2@latest` + +Update the bee tool with the following command: + +`go get -u github.com/beego/bee/v2@latest` + +`bee` is installed into `GOPATH/bin` by default. You need to add `GOPATH/bin` to your PATH, otherwise the `bee` command won't work. + +More details in [bee installation](./env.md) + +## bee tool commands + +Type `bee` in command line and the following messages with be displayed: + +``` +bee is a tool for managing Beego framework. + +Usage: + + bee command [arguments] + +The commands are: + + new Create a Beego application + run run the app and start a Web server for development + pack Compress a Beego project into a single file + api create an API Beego application + bale packs non-Go files to Go source files + version show the bee, Beego and Go version + generate source code generator + migrate run database migrations +``` + +### Command new + +The `new` command can create a new web project. You can create a new Beego project by typing `bee new ` under `$GOPATH/src`. This will generate all the default project folders and files: + +``` +bee new myproject +[INFO] Creating application... +/gopath/src/myproject/ +/gopath/src/myproject/conf/ +/gopath/src/myproject/controllers/ +/gopath/src/myproject/models/ +/gopath/src/myproject/static/ +/gopath/src/myproject/static/js/ +/gopath/src/myproject/static/css/ +/gopath/src/myproject/static/img/ +/gopath/src/myproject/views/ +/gopath/src/myproject/conf/app.conf +/gopath/src/myproject/controllers/default.go +/gopath/src/myproject/views/index.tpl +/gopath/src/myproject/main.go +13-11-25 09:50:39 [SUCC] New application successfully created! +``` + +``` +myproject +├── conf +│   └── app.conf +├── controllers +│   └── default.go +├── main.go +├── models +├── routers +│   └── router.go +├── static +│   ├── css +│   ├── img +│   └── js +├── tests +│   └── default_test.go +└── views + └── index.tpl + +8 directories, 4 files +``` + +### Command api + +The `new` command is used for crafting new web applications. The `api` command is used to create new API applications. +Here is the result of running `bee api project_name`: + +``` +bee api apiproject +create app folder: /gopath/src/apiproject +create conf: /gopath/src/apiproject/conf +create controllers: /gopath/src/apiproject/controllers +create models: /gopath/src/apiproject/models +create tests: /gopath/src/apiproject/tests +create conf app.conf: /gopath/src/apiproject/conf/app.conf +create controllers default.go: /gopath/src/apiproject/controllers/default.go +create tests default.go: /gopath/src/apiproject/tests/default_test.go +create models object.go: /gopath/src/apiproject/models/object.go +create main.go: /gopath/src/apiproject/main.go +``` + +Below is the generated project structure of a new API application: + +``` +apiproject +├── conf +│ └── app.conf +├── controllers +│ └── object.go +│ └── user.go +├── docs +│ └── doc.go +├── main.go +├── models +│ └── object.go +│ └── user.go +├── routers +│ └── router.go +└── tests + └── default_test.go +``` + +Compare this to the `bee new myproject` command seen earlier. +Note that the new API application doesn't have a `static` and `views` folder. + +You can also create a model and controller based on the database schema by providing database conn: + +`bee api [appname] [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test]` + +### Command run + +The `bee run` command will supervise the file system of any Beego project using [inotify](http://en.wikipedia.org/wiki/Inotify). The results will autocompile and display immediately after any modification in the Beego project folders. + +``` +13-11-25 09:53:04 [INFO] Uses 'myproject' as 'appname' +13-11-25 09:53:04 [INFO] Initializing watcher... +13-11-25 09:53:04 [TRAC] Directory(/gopath/src/myproject/controllers) +13-11-25 09:53:04 [TRAC] Directory(/gopath/src/myproject/models) +13-11-25 09:53:04 [TRAC] Directory(/gopath/src/myproject) +13-11-25 09:53:04 [INFO] Start building... +13-11-25 09:53:16 [SUCC] Build was successful +13-11-25 09:53:16 [INFO] Restarting myproject ... +13-11-25 09:53:16 [INFO] ./myproject is running... +``` +Visting `http://localhost:8080/` with a web browser will display your app running: + +![](./img/beerun.png) + +After modifying the `default.go` file in the `controllers` folder, the following output will be displayed in the command line: + +``` +13-11-25 10:11:20 [EVEN] "/gopath/src/myproject/controllers/default.go": DELETE|MODIFY +13-11-25 10:11:20 [INFO] Start building... +13-11-25 10:11:20 [SKIP] "/gopath/src/myproject/controllers/default.go": CREATE +13-11-25 10:11:23 [SKIP] "/gopath/src/myproject/controllers/default.go": MODIFY +13-11-25 10:11:23 [SUCC] Build was successful +13-11-25 10:11:23 [INFO] Restarting myproject ... +13-11-25 10:11:23 [INFO] ./myproject is running... +``` + +Refresh the browser to show the results of the new modifications. + + +### Command pack + +The `pack` command is used to compress the project into a single file. The compressed file can be deployed by uploading and extracting the zip file to the server. + +``` +bee pack +app path: /gopath/src/apiproject +GOOS darwin GOARCH amd64 +build apiproject +build success +exclude prefix: +exclude suffix: .go:.DS_Store:.tmp +file write to `/gopath/src/apiproject/apiproject.tar.gz` +``` + +The compressed file will be in the project folder: + +``` +rwxr-xr-x 1 astaxie staff 8995376 11 25 22:46 apiproject +-rw-r--r-- 1 astaxie staff 2240288 11 25 22:58 apiproject.tar.gz +drwxr-xr-x 3 astaxie staff 102 11 25 22:31 conf +drwxr-xr-x 3 astaxie staff 102 11 25 22:31 controllers +-rw-r--r-- 1 astaxie staff 509 11 25 22:31 main.go +drwxr-xr-x 3 astaxie staff 102 11 25 22:31 models +drwxr-xr-x 3 astaxie staff 102 11 25 22:31 tests +``` + +### Command bale + +This command is currently only available to the developer team. It is used to compress all static files in to a single binary file so that they do not need to carry static files including js, css, images and views when publishing the project. Those files will be self-extracting with non-overwrite when the program starts. + +### Command version + +This command displays the version of `bee`, `beego`, and `go`. + +```shell +$ bee version +bee :1.2.2 +Beego :1.4.2 +Go :go version go1.3.3 darwin/amd64 +``` + +This command try to output beego's version. It works well for GOPATH mode. Bee finds beego's version from $GOPATH/src/astaxie/beego directory. + +So when we use GOMOD mode, and we don't download beego's source code, Bee could not find the version's information. + +### Command generate +This command will generate the routers by analyzing the functions in controllers. + + +``` +bee generate scaffold [scaffoldname] [-fields=""] [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"] + The generate scaffold command will do a number of things for you. + -fields: a list of table fields. Format: field:type, ... + -driver: [mysql | postgres | sqlite], the default is mysql + -conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test + example: bee generate scaffold post -fields="title:string,body:text" + +bee generate model [modelname] [-fields=""] + generate RESTful model based on fields + -fields: a list of table fields. Format: field:type, ... + +bee generate controller [controllerfile] + generate RESTful controllers + +bee generate view [viewpath] + generate CRUD view in viewpath + +bee generate migration [migrationfile] [-fields=""] + generate migration file for making database schema update + -fields: a list of table fields. Format: field:type, ... + +bee generate docs + generate swagger doc file + +bee generate routers [-ctrlDir=/path/to/controller/directory] [-routersFile=/path/to/routers/file.go] [-routersPkg=myPackage] + -ctrlDir: the directory contains controllers definition. Bee scans this directory and its subdirectory to generate routers info + -routersFile: output file. All generated routers info will be output into this file. + If file not found, Bee create new one, or Bee truncates it. + The default value is "routers/commentRouters.go" + -routersPkg: package declaration.The default value is "routers". + When you pass routersFile parameter, you'd better pass this parameter + +bee generate test [routerfile] + generate testcase + +bee generate appcode [-tables=""] [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"] [-level=3] + generate appcode based on an existing database + -tables: a list of table names separated by ',', default is empty, indicating all tables + -driver: [mysql | postgres | sqlite], the default is mysql + -conn: the connection string used by the driver. + default for mysql: root:@tcp(127.0.0.1:3306)/test + default for postgres: postgres://postgres:postgres@127.0.0.1:5432/postgres + -level: [1 | 2 | 3], 1 = models; 2 = models,controllers; 3 = models,controllers,router +``` + + +### Command migrate +This command will run database migration scripts. + +``` +bee migrate [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"] + run all outstanding migrations + -driver: [mysql | postgresql | sqlite], the default is mysql + -conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test + +bee migrate rollback [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"] + rollback the last migration operation + -driver: [mysql | postgresql | sqlite], the default is mysql + -conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test + +bee migrate reset [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"] + rollback all migrations + -driver: [mysql | postgresql | sqlite], the default is mysql + -conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test + +bee migrate refresh [-driver=mysql] [-conn="root:@tcp(127.0.0.1:3306)/test"] + rollback all migrations and run them all again + -driver: [mysql | postgresql | sqlite], the default is mysql + -conn: the connection string used by the driver, the default is root:@tcp(127.0.0.1:3306)/test +``` + + +## bee tool configuration + +The file `bee.json` in the bee tool source code folder is the Beego configuration file. This file is still under development, but some options are already available to use: + +- `"version": 0`: version of file, for checking incompatible format version. +- `"go_install": false`: if you use a full import path like `github.com/user/repo/subpkg` you can enable this option to run `go install` and speed up you build processes. +- `"watch_ext": []`: add other file extensions to watch (only watch `.go` files by default). For example, `.ini`, `.conf`, etc. +- `"dir_structure":{}`: if your folder names are not the same as MVC classic names you can use this option to change them. +- `"cmd_args": []`: add command arguments for every start. +- `"envs": []`: set environment variables for every start. diff --git a/docs/en-US/v2.3.x/bee/env.md b/docs/en-US/v2.3.x/bee/env.md new file mode 100644 index 0000000..997097b --- /dev/null +++ b/docs/en-US/v2.3.x/bee/env.md @@ -0,0 +1,61 @@ +--- +name: Bee Environment Variable +lang: en-US +--- + +## Configure Bee on Mac or Linux + +`bee` relies on Go development environment, so you must install the Go development environment. If you do not install the Go, please install it. + +- Go download: https://go.dev or https://golang.org + +After completing the installation, make sure that you can use the `go` + command in your terminal, or you have to put the installation directory into the PATH environment variable. + +Here is the example of changing the PATH variable which works for Linux and Mac: +- If you use `bash`, check the file `~/.bashrc` or `~/.bash_profile` +- If you use `Zsh`, check the file `~/.zshrc` +- For Linux, you may need to check file `/etc/profile` + +Also, you can run the command `echo $SHELL` to output the shell installed on your computer. + +```bash +# go installation directory +export GOROOT=/usr/local/go +# GOPATH in general you don't need to change it. +# it's the directory that you want to store the Go projects. +export GOPATH=/Users/ding/go_workspace +# GOBIN in general you don't need to change it +# when you run the command `go install`, the specific tool will be added here +export GOBIN=$GOPATH/bin +# Currently you don't need to use this unless you are using the Go <= v1.14 +export GO111MODULE=on +# GO proxy which will download the dependencies from this website. +# Sometimes your company has its own proxy, you can change this +export GOPROXY=https://goproxy.cn,direct +# Don't forget this +export PATH=$PATH:$GOROOT/bin:$GOBIN +``` + +Add this into the shell configure file and then reload the configure file(or open a new terminal) + +## Configure Bee on Windows + +> TODO: missing English pictures + + +![golang_env](./img/env1.png) +![golang_env](./img/env2.png) +![golang_env](./img/env3.png) + +## bee installation + +Run the command to install `bee`: + +`go get github.com/beego/bee/v2@latest` + +And then run the command `bee version`: + +![bee_test](./img/bee_test.png) + +This indicates installing the `bee` successfully. \ No newline at end of file diff --git a/docs/en-US/v2.3.x/bee/img/bee_test.png b/docs/en-US/v2.3.x/bee/img/bee_test.png new file mode 100644 index 0000000..30ae24a Binary files /dev/null and b/docs/en-US/v2.3.x/bee/img/bee_test.png differ diff --git a/docs/en-US/v2.3.x/bee/img/beerun.png b/docs/en-US/v2.3.x/bee/img/beerun.png new file mode 100644 index 0000000..933ee5c Binary files /dev/null and b/docs/en-US/v2.3.x/bee/img/beerun.png differ diff --git a/docs/en-US/v2.3.x/bee/img/env1.png b/docs/en-US/v2.3.x/bee/img/env1.png new file mode 100644 index 0000000..2d49210 Binary files /dev/null and b/docs/en-US/v2.3.x/bee/img/env1.png differ diff --git a/docs/en-US/v2.3.x/bee/img/env2.png b/docs/en-US/v2.3.x/bee/img/env2.png new file mode 100644 index 0000000..1556342 Binary files /dev/null and b/docs/en-US/v2.3.x/bee/img/env2.png differ diff --git a/docs/en-US/v2.3.x/bee/img/env3.png b/docs/en-US/v2.3.x/bee/img/env3.png new file mode 100644 index 0000000..68cde2b Binary files /dev/null and b/docs/en-US/v2.3.x/bee/img/env3.png differ diff --git a/docs/en-US/v2.3.x/cache/README.md b/docs/en-US/v2.3.x/cache/README.md new file mode 100644 index 0000000..e9e97a3 --- /dev/null +++ b/docs/en-US/v2.3.x/cache/README.md @@ -0,0 +1,139 @@ +--- +title: Cache module +lang: zh +--- + +# Cache module + +- [cache pattern](./cache_pattern.md) + +The cache module of beego is used for data caching. The design idea comes from `database/sql`. It currently supports four engines: file, memcache, memory and redis. The installation method is as follows:: + + go get github.com/beego/beego-cache + +>>>if you use memcached or redis driver, you need to manually install the import package + + go get -u github.com/beego/beego/v2/client/cache/memcache + +>>>the package needs to be imported where it is used + + import _ "github.com/beego/beego/v2/client/cache/memcache" + +# Started + +first import the package: + +```go +import ( + "github.com/beego/beego/v2/client/cache" +) +``` + +# Cache type + +Four different types are currently supported, and the following describes how to create these four caches: + +- memory + + As shown below, the parameter information indicates the GC time, which means that an expired cleanup will be performed every 60s: + + bm := cache.NewMemoryCache(60) + +- file + + The parameters of FileCache are configured through the option pattern, `FileCacheWithCachePath` indicates the file directory of the configuration cache, `FileCacheWithFileSuffix` indicates the configuration file suffix, `FileCacheWithDirectoryLevel` indicates the configuration directory level, and `FileCacheWithEmbedExpiry` indicates the configuration expiration time: + + bm, err := NewFileCache( + FileCacheWithCachePath("cache"), + FileCacheWithFileSuffix(".bin"), + FileCacheWithDirectoryLevel(2), + FileCacheWithEmbedExpiry(120)) + +- redis + + As shown below, redis uses the library [redigo](https://github.com/garyburd/redigo/tree/master/redis); Note that users need to create Redis Cache's dependency redis.Pool in advance, and then use it as initialization Parameters of Redis Cache。 + + ```go + func main() { + dsn := "127.0.0.1:6379" + password := "123456" + dbNum := 0 + dialFunc := func() (c redis.Conn, err error) { + c, err = redis.Dial("tcp", dsn) + if err != nil { + return nil, berror.Wrapf(err, cache.DialFailed, + "could not dial to remote server: %s ", dsn) + } + + if password != "" { + if _, err = c.Do("AUTH", password); err != nil { + _ = c.Close() + return nil, err + } + } + + _, selecterr := c.Do("SELECT", dbNum) + if selecterr != nil { + _ = c.Close() + return nil, selecterr + } + return + } + // initialize a new pool + pool := &redis.Pool{ + Dial: dialFunc, + MaxIdle: 3, + IdleTimeout: 3 * time.Second, + } + + bm := NewRedisCache(pool) + } + ``` + + * dsn: for the connection ip and port + * dbNum: the DB number when connecting to Redis. The default is 0 + * password: for connecting to a Redis server with a password + + +- memcache + + memcache uses [vitess library](https://github.com/youtube/vitess/tree/master/go/memcache), dns indicates the connection address of memcache: + + pool := memcache.New(dsn) + bm := NewMemCache(pool) + +then we can use bm to add, delete, and modify the cache: + +```go +bm.Put(context.TODO(), "astaxie", 1, 10*time.Second) +bm.Get(context.TODO(), "astaxie") +bm.IsExist(context.TODO(), "astaxie") +bm.Delete(context.TODO(), "astaxie") +``` + +>>> The first parameter is the context of the Go language. We introduce the context parameter to support observability (tracing, metrics) + +# Develop your own engine + +The cache module is implemented in the form of an interface, so users can easily implement the interface, and then register to implement their own Cache engine: + +```go +type Cache interface { + // Get a cached value by key. + Get(ctx context.Context, key string) (interface{}, error) + // GetMulti is a batch version of Get. + GetMulti(ctx context.Context, keys []string) ([]interface{}, error) + // Set a cached value with key and expire time. + Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error + // Delete cached value by key. + Delete(ctx context.Context, key string) error + // Increment a cached int value by key, as a counter. + Incr(ctx context.Context, key string) error + // Decrement a cached int value by key, as a counter. + Decr(ctx context.Context, key string) error + // Check if a cached value exists or not. + IsExist(ctx context.Context, key string) (bool, error) + // Clear all cache. + ClearAll(ctx context.Context) error +} +``` diff --git a/docs/en-US/v2.3.x/cache/cache_pattern.md b/docs/en-US/v2.3.x/cache/cache_pattern.md new file mode 100644 index 0000000..e44d194 --- /dev/null +++ b/docs/en-US/v2.3.x/cache/cache_pattern.md @@ -0,0 +1,225 @@ +--- +title: Cache Pattern +lang: en-US +--- + +We provide support for some cache pattern in cache module, to solve or alleviate the problems of cache hotspot nvalid, penetration, and avalanche. + +These cache pattern are implemented using decorator design pattern. + + +## Read Through + +Read Through cache pattern essentially helps you load data from the database without caching it, and write the updated data back to the cache. +```go +package main + +import ( + "context" + "fmt" + "github.com/beego/beego/v2/client/cache" + "log" + "time" +) + +func main() { + var c cache.Cache = cache.NewMemoryCache() + var err error + c, err = cache.NewReadThroughCache(c, + // expiration, same as the expiration of key + time.Minute, + // load func, how to load data if the key is absent. + // in general, you should load data from database. + func(ctx context.Context, key string) (any, error) { + return fmt.Sprintf("hello, %s", key), nil + }) + if err != nil { + panic(err) + } + + val, err := c.Get(context.Background(), "Beego") + if err != nil { + panic(err) + } + // print hello, Beego + fmt.Print(val) +} +``` +NewReadThroughCache takes two arguments: +- c Cache: the Cache instance +- expiration time.Duration: the expiration +- loadFunc func(ctx context.Context, key string) (any, error): Using this function to load data if the key is not present in Cache + +In the previous example, when the key is not found, we directly return the concatenated string. In normal situations, data is loaded from the database in production environments. + +**For this implementation, if the "Get" method is called on the "c" object and returns nil or err is not nil, it will attempt to load the data.**。 + +## Write Through + +```go +package main + +import ( + "context" + "fmt" + "github.com/beego/beego/v2/client/cache" + "time" +) + +func main() { + c := cache.NewMemoryCache() + wtc, err := cache.NewWriteThroughCache(c, func(ctx context.Context, key string, val any) error { + fmt.Printf("write data to somewhere key %s, val %v \n", key, val) + return nil + }) + if err != nil { + panic(err) + } + err = wtc.Set(context.Background(), + "/biz/user/id=1", "I am user 1", time.Minute) + if err != nil { + panic(err) + } + // it will print write data to somewhere key /biz/user/id=1, val I am user 1 +} +``` + +NewWriteThroughCache takes two argument: +- c Cache:the Cache instance +- fn func(ctx context.Context, key string, val any): store data function + +WriteThroughCache will call the `fn` and then update the cache。 + +Note that "WriteThroughCache" does not solve the consistency problem completely, and you should use it with caution. + +## Random Expire + +This pattern is mainly used to solve the problem of cache avalanche, where a large number of keys expire at the same time. Therefore, it can be considered to add a random offset to the expiration time when setting the key-value. This can prevent the entire cache from expiring at the same time, thus avoiding the problem of cache avalanche. + +```go +package main + +import ( + "context" + "fmt" + "github.com/beego/beego/v2/client/cache" + "math/rand" + "time" +) + +func main() { + mc := cache.NewMemoryCache() + // use the default strategy which will generate random time offset (range: [3s,8s)) expired + c := cache.NewRandomExpireCache(mc) + // so the expiration will be [1m3s, 1m8s) + err := c.Put(context.Background(), "hello", "world", time.Minute) + if err != nil { + panic(err) + } + + c = cache.NewRandomExpireCache(mc, + // based on the expiration + cache.WithRandomExpireOffsetFunc(func() time.Duration { + val := rand.Int31n(100) + fmt.Printf("calculate offset %d", val) + return time.Duration(val) * time.Second + })) + + // so the expiration will be [1m0s, 1m100s) + err = c.Put(context.Background(), "hello", "world", time.Minute) + if err != nil { + panic(err) + } +} +``` +NewRandomExpireCache by default adds a random offset to the expiration time in the range [3s, 8s]. This offset is suitable for cases where the data volume is small and the expiration time is in the few-minute range. If you need a more complex caching strategy, you can use the "WithRandomExpireOffsetFunc" option. + +Of course, the "WithRandomExpireOffsetFunc" option has limitations. If it does not meet your needs, you can write a similar implementation yourself. For example, you can add a percentage offset to the expiration time of a key based on the expected expiration time of the key. For example, add a 1% random offset to the expiration time. + +## Singleflight + +In cases where the key does not exist or the cache is not found, multiple goroutines may be launched to load the data. Using this pattern can ensure that there is only one goroutine launched to load data for a key in the current process. + +```go +package main + +import ( + "context" + "fmt" + "github.com/beego/beego/v2/client/cache" + "time" +) + +func main() { + c := cache.NewMemoryCache() + c, err := cache.NewSingleflightCache(c, time.Minute, func(ctx context.Context, key string) (any, error) { + return fmt.Sprintf("hello, %s", key), nil + }) + if err != nil { + panic(err) + } + val, err := c.Get(context.Background(), "Beego") + if err != nil { + panic(err) + } + // it will output hello, Beego + fmt.Print(val) +} +``` + +"NewSingleflightCache" and "NewReadThroughCache" have the same parameter meaning. These two cache patterns use the same parameters to configure the cache. + +However, it is important to note that there is no impact between different keys, and there is also no impact between different Cache instances. For example, if there are fifty goroutines loading fifty different keys at this time, the final queries that land on the database will still be fifty. + +## Bloom Filter + +This mode is used to quickly determine whether the data corresponding to a key exists in a high-concurrency environment, and is particularly suitable for solving the problem of cache piercing. + +``` +package main + +import ( + "context" + "fmt" + "github.com/beego/beego/v2/client/cache" + "time" +) + +func main() { + c := cache.NewMemoryCache() + c, err := cache.NewBloomFilterCache(c, func(ctx context.Context, key string) (any, error) { + return fmt.Sprintf("hello, %s", key), nil + }, &AlwaysExist{}, time.Minute) + if err != nil { + panic(err) + } + + val, err := c.Get(context.Background(), "Beego") + if err != nil { + panic(err) + } + fmt.Println(val) +} + +type AlwaysExist struct { +} + +func (a *AlwaysExist) Test(data string) bool { + return true +} + +func (a *AlwaysExist) Add(data string) { + +} +``` +In this example, we passed a Bloom filter that always returns true (indicating that the data exists). In normal cases, in your business, you should typically implement a Bloom filter based on memory or based on Redis to achieve high performance. + + + + + + + + + + diff --git a/docs/en-US/v2.3.x/config/README.md b/docs/en-US/v2.3.x/config/README.md new file mode 100644 index 0000000..b45599b --- /dev/null +++ b/docs/en-US/v2.3.x/config/README.md @@ -0,0 +1,376 @@ +--- +title: Configure Module +lang: en-US +--- + +# Configure Module +Configure module is the core module which provides an abstraction layer for different configuration sources or formats. + +You can find the [examples here](https://github.com/beego/beego-example/tree/master/config) + +Currently, Beego support all major configure formats, including **INI(by default)**, XML, JSON, YAML and remote configure center `etcd`. + +[Config API](https://github.com/beego/beego/blob/develop/core/config/config.go): + +```go +// Configer defines how to get and set value from configuration raw data. +type Configer interface { + // support section::key type in given key when using ini type. + Set(key, val string) error + + // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. + String(key string) (string, error) + // get string slice + Strings(key string) ([]string, error) + Int(key string) (int, error) + Int64(key string) (int64, error) + Bool(key string) (bool, error) + Float(key string) (float64, error) + // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. + DefaultString(key string, defaultVal string) string + // get string slice + DefaultStrings(key string, defaultVal []string) []string + DefaultInt(key string, defaultVal int) int + DefaultInt64(key string, defaultVal int64) int64 + DefaultBool(key string, defaultVal bool) bool + DefaultFloat(key string, defaultVal float64) float64 + + // DIY return the original value + DIY(key string) (interface{}, error) + + GetSection(section string) (map[string]string, error) + + Unmarshaler(prefix string, obj interface{}, opt ...DecodeOption) error + Sub(key string) (Configer, error) + OnChange(key string, fn func(value string)) + SaveConfigFile(filename string) error +} +``` + +Notices: + +1. All `Default*` methods will return the default value if the key is not exist or got any error; +2. `DIY` returns the value directly without any conversion; +3. `GetSection` returns all configuration of the specific `section`, and it depends on the implementation details; +4. `Unmarshaler` tries to use the configuration value to initiate the `obj`。`prefix` is similar to `section`; +5. `Sub` is similar to `GetSection` which tries to return all configuration of the specific `section`。The difference is that `GetSection` returns the values as `map` but `Sub` returns the values as `Config` instance; +6. `OnChange` subscribes the change the configuration. But most of the implementations which is based on file system do not support this methods. In general we prefer to use this for configure center like etcd; +7. `SaveConfigFile` writes all configuration into file(s); +8. Some implementations support the key like `a.b.c` while some DO NOT. Besides, some implementations choose the `.` as separator while some choose other characters. This is a historical problem and we can not make them consistent if we keep backward compatible。 + +Web module re-encapsulate the configuration module, more details refer [Web Module Configuration](./../web/config.md) + +## Initiate + +There are two major ways to use the configuration module: + +- Uses package functions `config.XXXX` which relies on the global instance +- Initiates `Configer` instances + + +### Global instance + +Beego will try to parse the file `conf/app.conf` so that you can use the package functions: + +```go +import ( + "github.com/beego/beego/v2/core/config" + "github.com/beego/beego/v2/core/logs" +) + +func main() { + val, _ := config.String("name") + logs.Info("auto load config name is", val) +} +``` + +Or you can initiate the global instance manually to specify the source: + +```go +config.InitGlobalInstance("etcd", "etcd address") +``` + +### Initiates `Configer` instances + +If you do not want to use the global instance, you can initiate the `Configer` instances manually: + +```go +func main() { + cfg, err := config.NewConfig("ini", "my_config.ini") + if err != nil { + logs.Error(err) + } + val, _ := cfg.String("appname") + logs.Info("auto load config name is", val) +} +``` + +## Environment variable + +The format for this is `${ENVIRONMENTVARIABLE}` within the configuration file which is equivalent to `value = os.Getenv('ENVIRONMENTVARIABLE')`. Beego will only check for environment variables if the value begins with `${` and ends with `}`. + +Additionally, a default value can be configured for the case that there is no environment variable set or the environment variable is empty. This is accomplished by using the format `${ENVVAR||defaultvalue}`: + +```ini + runmode = "${ProRunMode||dev}" + httpport = "${ProPort||9090}" +``` + +## Implementations + + +Note that all relative file paths, are calculated from your working directory! +Second, except for the default INI implementation, all other implementations need to be introduced using anonymous introduction of the corresponding package. + +### INI + +INI is the default implementation for configuring modules. It also supports loading multiple configuration files using the `include` syntax。 + +app.ini: + +```ini + appname = beepkg + httpaddr = "127.0.0.1" + httpport = 9090 + + include "app2.ini" +``` + +app2.ini: + +```ini + runmode ="dev" + autorender = false + recoverpanic = false + viewspath = "myview" + + [dev] + httpport = 8080 + [prod] + httpport = 8088 + [test] + httpport = 8888 +``` + +```go +func main() { + cfg, err := config.NewConfig("ini", "app.ini") + if err != nil { + logs.Error(err) + } + val, _ := cfg.String("appname") + logs.Info("auto load config name is", val) +} +``` + +### JSON + +```go +import ( + "github.com/beego/beego/v2/core/config" + // DO NOT FORGET THIS + _ "github.com/beego/beego/v2/core/config/json" + "github.com/beego/beego/v2/core/logs" +) + +var ( + ConfigFile = "./app.json" +) + +func main() { + err := config.InitGlobalInstance("json", ConfigFile) + if err != nil { + logs.Critical("An error occurred:", err) + panic(err) + } + + val, _ := config.String("name") + + logs.Info("load config name is", val) +} +``` + +### YAML + +```go +import ( + "github.com/beego/beego/v2/core/config" + // never forget this + _ "github.com/beego/beego/v2/core/config/yaml" + "github.com/beego/beego/v2/core/logs" +) + +var ( + ConfigFile = "./app.yaml" +) + +func main() { + err := config.InitGlobalInstance("yaml", ConfigFile) + if err != nil { + logs.Critical("An error occurred:", err) + panic(err) + } + + val, _ := config.String("name") + + logs.Info("load config name is", val) +} +``` + +### XML + +```go +import ( + "github.com/beego/beego/v2/core/config" + // never forget this + _ "github.com/beego/beego/v2/core/config/xml" + "github.com/beego/beego/v2/core/logs" +) + +var ( + ConfigFile = "./app.xml" +) + +func main() { + err := config.InitGlobalInstance("xml", ConfigFile) + if err != nil { + logs.Critical("An error occurred:", err) + panic(err) + } + + val, _ := config.String("name") + + logs.Info("load config name is", val) +} +``` + +Note that all configuration items should be placed within the root `config`: + +```xml + + + beego + +``` + +### TOML + +```go +import ( + "github.com/beego/beego/v2/core/config" + // never forget this + _ "github.com/beego/beego/v2/core/config/toml" + "github.com/beego/beego/v2/core/logs" +) + +var ( + ConfigFile = "./app.toml" +) + +func main() { + err := config.InitGlobalInstance("toml", ConfigFile) + if err != nil { + logs.Critical("An error occurred:", err) + panic(err) + } + + val, _ := config.String("name") + + logs.Info("load config name is", val) +} +``` + +### Etcd + +```go +import ( + "github.com/beego/beego/v2/core/config" + // never forget this + _ "github.com/beego/beego/v2/core/config/toml" + "github.com/beego/beego/v2/core/logs" +) + +func main() { + err := config.InitGlobalInstance("etcd", "your_config") + if err != nil { + logs.Critical("An error occurred:", err) + panic(err) + } + + val, _ := config.String("name") + + logs.Info("load config name is", val) +} +``` + +where `your_config` is a JSON configuration that corresponds to: + +```go +type Config struct { + // Endpoints is a list of URLs. + Endpoints []string `json:"endpoints"` + + // AutoSyncInterval is the interval to update endpoints with its latest members. + // 0 disables auto-sync. By default auto-sync is disabled. + AutoSyncInterval time.Duration `json:"auto-sync-interval"` + + // DialTimeout is the timeout for failing to establish a connection. + DialTimeout time.Duration `json:"dial-timeout"` + + // DialKeepAliveTime is the time after which client pings the server to see if + // transport is alive. + DialKeepAliveTime time.Duration `json:"dial-keep-alive-time"` + + // DialKeepAliveTimeout is the time that the client waits for a response for the + // keep-alive probe. If the response is not received in this time, the connection is closed. + DialKeepAliveTimeout time.Duration `json:"dial-keep-alive-timeout"` + + // MaxCallSendMsgSize is the client-side request send limit in bytes. + // If 0, it defaults to 2.0 MiB (2 * 1024 * 1024). + // Make sure that "MaxCallSendMsgSize" < server-side default send/recv limit. + // ("--max-request-bytes" flag to etcd or "embed.Config.MaxRequestBytes"). + MaxCallSendMsgSize int + + // MaxCallRecvMsgSize is the client-side response receive limit. + // If 0, it defaults to "math.MaxInt32", because range response can + // easily exceed request send limits. + // Make sure that "MaxCallRecvMsgSize" >= server-side default send/recv limit. + // ("--max-request-bytes" flag to etcd or "embed.Config.MaxRequestBytes"). + MaxCallRecvMsgSize int + + // TLS holds the client secure credentials, if any. + TLS *tls.Config + + // Username is a user name for authentication. + Username string `json:"username"` + + // Password is a password for authentication. + Password string `json:"password"` + + // RejectOldCluster when set will refuse to create a client against an outdated cluster. + RejectOldCluster bool `json:"reject-old-cluster"` + + // DialOptions is a list of dial options for the grpc client (e.g., for interceptors). + // For example, pass "grpc.WithBlock()" to block until the underlying connection is up. + // Without this, Dial returns immediately and connecting the server happens in background. + DialOptions []grpc.DialOption + + // Context is the default client context; it can be used to cancel grpc dial out and + // other operations that do not have an explicit context. + Context context.Context + + // Logger sets client-side logger. + // If nil, fallback to building LogConfig. + Logger *zap.Logger + + // LogConfig configures client-side logger. + // If nil, use the default logger. + // TODO: configure gRPC logger + LogConfig *zap.Config + + // PermitWithoutStream when set will allow client to send keepalive pings to server without any active streams(RPCs). + PermitWithoutStream bool `json:"permit-without-stream"` + + // TODO: support custom balancer picker +} +``` diff --git a/docs/en-US/v2.3.x/environment/README.md b/docs/en-US/v2.3.x/environment/README.md new file mode 100644 index 0000000..6c5d1e8 --- /dev/null +++ b/docs/en-US/v2.3.x/environment/README.md @@ -0,0 +1,19 @@ +--- +title: Environment +lang: en-US +--- + +# Beego Environment + +`Beego` depends on the go development environment, and in the current version, `Beego` relies on the `go mod` feature. To install the go environment you can refer to: + +- [golang installation](https://golang.org/doc/install) + +During the development of Beego applications, we will need to use a number of commands: + +- [go mod](./go_mod.md) +- [go get](./go_get_command.md) + +We also strongly recommend that you use our companion tool `bee`: + +- [bee](../bee/README.md) diff --git a/docs/en-US/v2.3.x/environment/go_get_command.md b/docs/en-US/v2.3.x/environment/go_get_command.md new file mode 100644 index 0000000..c395ddd --- /dev/null +++ b/docs/en-US/v2.3.x/environment/go_get_command.md @@ -0,0 +1,35 @@ +--- +title: Go Get Introduction +lang: en-US +sidebar: auto +--- + +# Get Command + +## Specify Version + +We generally use the `go get` command to get the dependencies. For example, in the project root directory, execute: + +```shell +go get github.com/beego/beego/v2@v2.0.1 +``` + +It will download the dependency Beego with the specific version `v2.0.1`。On `github`, this part of the code corresponds to the source code with `tag` of `v2.0.1` in `github`. + +Refer [Beego tags](https://github.com/beego/beego/tags) to find out all tags. + +## Latest stable version + +Using `latest` to download the latest stable version: + +```shell +go get github.com/beego/beego/v2@latest +``` + +## Using specific branch + +For example, if you want to use some unstable feature, you can specify the development branch `develop`: + +```shell +go get github.com/beego/beego/v2@develop +``` diff --git a/docs/en-US/v2.3.x/environment/go_mod.md b/docs/en-US/v2.3.x/environment/go_mod.md new file mode 100644 index 0000000..87fbd24 --- /dev/null +++ b/docs/en-US/v2.3.x/environment/go_mod.md @@ -0,0 +1,17 @@ +--- +title: Go Mod +lang: en-US +sidebar: auto +--- + +`Beego` strongly relies on the `go mod` command, here we briefly introduce the following `go mod` commands for everyday use. + +Details can be found by typing: + +```shell +go help mod +``` + +### go mod tidy + +This command will add missing dependencies or remove unused dependencies. In general, we recommend executing this command after creating a project, or after adding a new dependency. diff --git a/docs/en-US/v2.3.x/environment/install_go_linux.md b/docs/en-US/v2.3.x/environment/install_go_linux.md new file mode 100644 index 0000000..297b95b --- /dev/null +++ b/docs/en-US/v2.3.x/environment/install_go_linux.md @@ -0,0 +1,6 @@ +--- +title: Linux install GO +lang: en-US +--- + +TODO diff --git a/docs/en-US/v2.3.x/environment/install_go_mac.md b/docs/en-US/v2.3.x/environment/install_go_mac.md new file mode 100644 index 0000000..fafa943 --- /dev/null +++ b/docs/en-US/v2.3.x/environment/install_go_mac.md @@ -0,0 +1,6 @@ +--- +title: Mac install Go +lang: en-US +--- + +TODO diff --git a/docs/en-US/v2.3.x/environment/install_go_windows.md b/docs/en-US/v2.3.x/environment/install_go_windows.md new file mode 100644 index 0000000..723f53d --- /dev/null +++ b/docs/en-US/v2.3.x/environment/install_go_windows.md @@ -0,0 +1,6 @@ +--- +title: Window install GO +lang: en-US +--- + +TODO diff --git a/docs/en-US/v2.3.x/i18n/README.md b/docs/en-US/v2.3.x/i18n/README.md new file mode 100644 index 0000000..8473fa7 --- /dev/null +++ b/docs/en-US/v2.3.x/i18n/README.md @@ -0,0 +1,239 @@ +--- +title: i18n +lang: en-US +--- + +# i18n + +This module is mainly used for i18n of sites and applications, which provides multiple-language options to users, improve user experience. + +You can use the following command to install this module: + +`go get github.com/beego/i18n@latest` + +## i18n Usage + +First of all, you have to import this package: + + import ( + "github.com/beego/i18n" + ) + +The format of locale files is very much like the INI format configuration file, which is basically key-value pairs. But this module has some improvements. Every language corresponds to a locale file, for example, under `conf` folder of beego.vip, there are two files called `locale_en-US.ini` and `locale_zh-CN.ini`. + +The name and extensions of locale files can be anything. + +## Minimal example + +Here are two simplest locale file examples: + +File `locale_en-US.ini`: + +``` +hi = hello +bye = goodbye +``` + +File `locale_zh-CN.ini`: + +``` +hi = 您好 +bye = 再见 +``` + +### Use in controller + +For every request, Beego uses individual goroutines to handle the request; therefore, you can embed an `i18n.Locale` as an anonymous field to process locale operations of the current request. This requires that you understand the idea of `baseController` and `Prepare` method. See source file `routers/router.go` of beego.vip for more details. + +After accepting the request, use the `Prepare` method of `baseController` to do language operations, which you only need to write the same code once and use it in all the upper level controllers. + +#### Register locale files + +The following code is from beego.vip source file `routers/init.go`: + +```go +// Initialized language type list. +langs := strings.Split(beego.AppConfig.String("lang::types"), "|") +names := strings.Split(beego.AppConfig.String("lang::names"), "|") +langTypes = make([]*langType, 0, len(langs)) +for i, v := range langs { + langTypes = append(langTypes, &langType{ + Lang: v, + Name: names[i], + }) +} + +for _, lang := range langs { + beego.Trace("Loading language: " + lang) + if err := i18n.SetMessage(lang, "conf/"+"locale_"+lang+".ini"); err != nil { + beego.Error("Fail to set message file: " + err.Error()) + return + } +} +``` + +In this piece of code, we get languages that we want to support in the configuration file, in this case, we have `en-US` and `zh-CN`. Then we initialize a slice for users to change language option(not discussed here). Finally, we call the `i18n.SetMessage` function in a loop to load all the locale files. Here you can see why we recommend the you use the naming conventions of beego.vip for locale files. + +#### Initialize controller language + +The following code is from the beego.vip source file `routers/router.go`, which decides on which user language option to use in the following order: 1: URL specified 2: Cookies and 3: browser `Accept-Language`. + +```go +// setLangVer sets site language version. +func (this *baseRouter) setLangVer() bool { + isNeedRedir := false + hasCookie := false + + // 1. Check URL arguments. + lang := this.Input().Get("lang") + + // 2. Get language information from cookies. + if len(lang) == 0 { + lang = this.Ctx.GetCookie("lang") + hasCookie = true + } else { + isNeedRedir = true + } + + // Check again in case someone modify on purpose. + if !i18n.IsExist(lang) { + lang = "" + isNeedRedir = false + hasCookie = false + } + + // 3. Get language information from 'Accept-Language'. + if len(lang) == 0 { + al := this.Ctx.Request.Header.Get("Accept-Language") + if len(al) > 4 { + al = al[:5] // Only compare first 5 letters. + if i18n.IsExist(al) { + lang = al + } + } + } + + // 4. Default language is English. + if len(lang) == 0 { + lang = "en-US" + isNeedRedir = false + } + + curLang := langType{ + Lang: lang, + } + + // Save language information in cookies. + if !hasCookie { + this.Ctx.SetCookie("lang", curLang.Lang, 1<<31-1, "/") + } + + restLangs := make([]*langType, 0, len(langTypes)-1) + for _, v := range langTypes { + if lang != v.Lang { + restLangs = append(restLangs, v) + } else { + curLang.Name = v.Name + } + } + + // Set language properties. + this.Lang = lang + this.Data["Lang"] = curLang.Lang + this.Data["CurLang"] = curLang.Name + this.Data["RestLangs"] = restLangs + + return isNeedRedir +} +``` + +The variable `isNeedRedir` indicates whether user uses URL to specify the language option. To keep the URL clean, beego.vip automatically sets the value in cookies and redirect. + +The line `this.Data["Lang"] = curLang.Lang` sets user language option to template variable `Lang` so that we can handle language in template files. + +Following two lines: + + this.Data["CurLang"] = curLang.Name + this.Data["RestLangs"] = restLangs + +For users to change language option, see beego.vip source code for more details. + +#### Handle language in controller + +While the `i18n.Locale` is an anonymous field to be embedded in `baseController`, we can use `this.Tr(format string, args ...interface{})` to handle language in controller. + +### Handle language in template + +By passing template variable `Lang` to indicate language option, you are able to do localization in template. But before that, you need to register a template function. + +Following code is from beego.vip source file `beeweb.go`: + + beego.AddFuncMap("i18n", i18n.Tr) + +After that, do the following with `Lang` to handle language: +``` +{{i18n .Lang "hi%d" 12}} +``` + +Code above will produce: + +- English `en-US`:`hello12` +- Chinese `zh-CN`:`您好12` + +## Section + +For different pages, one key may map to different values. Therefore, i18n module also uses the section feature of INI format configuration to achieve section separation. + +For example, the key name is `about`, and we want to show `About` in the home page and `About Us` in about page. Then you can do following: + +Content in locale file: + +``` +about = About + +[about] +about = About Us +``` + +Get `about` in home page: + +``` +{{i18n .Lang "about"}} +``` + +Get `about` in about page: + +``` +{{i18n .Lang "about.about"}} +``` + +### Ambiguity + +Because dot `.` denotes a section in both [INI parser](https://github.com/Unknwon/goconfig) and locale files, when your key name contains `.` this will cause ambiguity. To avoid ambiguity, you just need to add one more `.` in front of the key. + +For example, the key name is `about.`, then we can use: + +``` +{{i18n .Lang ".about."}} +``` + +to get the desired result. + +## Helper tool + +Module i18n provides a command line helper tool beei18n to simplify the steps in your development. You can install it as follows: + + go get github.com/beego/i18n/beei18n + +### Sync locale files + +Command `sync` allows you use a exist local file as the template to create or sync other locale files: + + beei18n sync source_file.ini other1.ini other2.ini + +This command can operate on 1 or more files in one command. + +## More information + +If the key does not exist, then i18n will return the key string to caller. For instance, when key name is `hi` and it does not exist in locale file, it will simply return `hi` as output. + diff --git a/docs/en-US/v2.3.x/logs/README.md b/docs/en-US/v2.3.x/logs/README.md new file mode 100644 index 0000000..5902d4b --- /dev/null +++ b/docs/en-US/v2.3.x/logs/README.md @@ -0,0 +1,234 @@ +--- +title: Log Module +lang: en-US +--- + +# Logging + +The logging module is inspired by `database/sql`. It supports file, console, net and smtp as destination providers by default. It is installed like this: + + go get github.com/beego/beego/v2/core/logs + +## Basic Usage + +### General Usage +Import package: + + import ( + "github.com/beego/beego/v2/core/logs" + ) + +Initialize log variable (10000 is the cache size): + + log := logs.NewLogger(10000) + +Then add the output provider (it supports outputting to multiple providers at the same time). The first parameter is the provider name (`console`, `file`,`multifile`, `conn` , `smtp` or `es`). + + log.SetLogger("console") + +The second parameter is a provider-specific configuration string (see below for details). +logs.SetLogger(logs.AdapterFile,`{"filename":"project.log","level":7,"maxlines":0,"maxsize":0,"daily":true,"maxdays":10,"color":true}`) + + +Then we can use it in our code: + + + package main + + import ( + "github.com/beego/beego/v2/core/logs" + ) + + func main() { + //an official log.Logger + l := logs.GetLogger() + l.Println("this is a message of http") + //an official log.Logger with prefix ORM + logs.GetLogger("ORM").Println("this is a message of orm") + + logs.Debug("my book is bought in the year of ", 2016) + logs.Info("this %s cat is %v years old", "yellow", 3) + logs.Warn("json is a type of kv like", map[string]int{"key": 2016}) + logs.Error(1024, "is a very", "good game") + logs.Critical("oh,crash") + } + +### Another Way + +beego/logs supports to declare a single logger to use + + + package main + + import ( + "github.com/beego/beego/v2/core/logs" + ) + + func main() { + log := logs.NewLogger() + log.SetLogger(logs.AdapterConsole) + log.Debug("this is a debug message") + } + +## Logging caller information (file name & line number) + +The module can be configured to include the file & line number of the log calls in the logging output. This functionality is disabled by default, but can be enabled using the following code: + + logs.EnableFuncCallDepth(true) + +Use `true` to turn file & line number logging on, and `false` to turn it off. Default is `false`. + +If your application encapsulates the call to the log methods, you may need use `SetLogFuncCallDepth` to set the number of stack frames to be skipped before the caller information is retrieved. The default is 2. + + logs.SetLogFuncCallDepth(3) + +## Logging asynchronously + +You can set logger to asynchronous logging to improve performance: + + logs.Async() + +Add a parameter to set the length of buffer channel +logs.Async(1e3) + +## Provider configuration + +Each provider supports a set of configuration options. + +- console + + Can set output level or use default. Uses `os.Stdout` by default. + + logs.SetLogger(logs.AdapterConsole, `{"level":1}`) + +- file + + E.g.: + + logs.SetLogger(logs.AdapterFile, `{"filename":"test.log"}`) + + Parameters: + - filename: Save to filename. + - maxlines: Maximum lines for each log file, 1000000 by default. + - maxsize: Maximum size of each log file, 1 << 28 or 256M by default. + - daily: If log rotates by day, true by default. + - maxdays: Maximum number of days log files will be kept, 7 by default. + - rotate: Enable logrotate or not, true by default. + - level: Log level, Trace by default. + - perm: Log file permission + +- multifile + + E.g.: + + logs.SetLogger(logs.AdapterMultiFile, ``{"filename":"test.log","separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]}``) + + Parameters: + - filename: Save to filename. + - maxlines: Maximum lines for each log file, 1000000 by default. + - maxsize: Maximum size of each log file, 1 << 28 or 256M by default. + - daily: If log rotates by day, true by default. + - maxdays: Maximum number of days log files will be kept, 7 by default. + - rotate: Enable logrotate or not, true by default. + - level: Log level, Trace by default. + - perm: Log file permission + - separate: Log file will separate to test.error.log/test.debug.log as the log level set in the json array + +- conn + + Net output: + + logs.SetLogger(logs.AdapterConn, `{"net":"tcp","addr":":7020"}`) + + Parameters: + - reconnectOnMsg: If true: reopen and close connection every time a message is sent. False by default. + - reconnect: If true: auto connect. False by default. + - net: connection type: tcp, unix or udp. + - addr: net connection address. + - level: Log level, Trace by default. + +- smtp + + Log by email: + + logs.SetLogger(logs.AdapterMail, `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`) + + Parameters: + - username: smtp username. + - password: smtp password. + - host: SMTP server host. + - sendTos: email addresses to which the logs will be sent. + - subject: email subject, `Diagnostic message from server` by default. + - level: Log level, Trace by default. + + +- ElasticSearch + + Log to ElasticSearch: + + logs.SetLogger(logs.AdapterEs, `{"dsn":"http://localhost:9200/","level":1}`) + +- JianLiao + + Log to JianLiao + + logs.SetLogger(logs.AdapterJianLiao, `{"authorname":"xxx","title":"beego", "webhookurl":"https://jianliao.com/xxx", "redirecturl":"https://jianliao.com/xxx","imageurl":"https://jianliao.com/xxx","level":1}`) + +- Slack + + Log to Slack + + logs.SetLogger(logs.AdapterSlack, `{"webhookurl":"https://slack.com/xxx","level":1}`) + +## Custom format logging + +A new feature of the 2.0 release of beego is the ability to have custom formatting applied to your logs before being sent to your preferred adapter. Here is an example of it in use: +```go +package main + +import ( + "fmt" + + beego "github.com/beego/beego/v2/pkg" + "github.com/beego/beego/v2/pkg/logs" +) + +type MainController struct { + beego.Controller +} + + +func customFormatter(lm *logs.LogMsg) string { + return fmt.Sprintf("[CUSTOM FILE LOGGING] %s", lm.Msg) +} + +func GlobalFormatter(lm *logs.LogMsg) string { + return fmt.Sprintf("[GLOBAL] %s", lm.Msg) +} + +func main() { + + beego.BConfig.Log.AccessLogs = true + + // GlobalFormatter only overrides default log adapters. Hierarchy is like this: + // adapter specific formatter > global formatter > default formatter + logs.SetGlobalFormatter(GlobalFormatter) + + logs.SetLogger("console", "") + + logs.SetLoggerWithOpts("file", []string{`{"filename":"test.json"}`}, customFormatter) + + beego.Run() +} +``` +## Global formatter +With the global formatter you can override and *default* logging formatters. This means that setting a global formatter will override any `logs.SetLogger()` adapters but will not override and `logs.SetLoggerWithOpts()` adapters. Default logging formatters are any adapters set using the following syntax: +```go +logs.SetLogger("adapterName", '{"key":"value"}') +``` + +## Adapter Specific formatters +Apapter specific formatters can be set and will override any default or global formatter that has been set for a given adapter. Adapter specific logging formatters can be set using the following syntax: +```go +logs.SetLoggerWithOpts("adapterName", []string{'{"key":"value"}'}, utils.KV{Key:"formatter", Value: formatterFunc}) +``` \ No newline at end of file diff --git a/docs/en-US/v2.3.x/orm/README.md b/docs/en-US/v2.3.x/orm/README.md new file mode 100644 index 0000000..5a97615 --- /dev/null +++ b/docs/en-US/v2.3.x/orm/README.md @@ -0,0 +1,80 @@ +--- +title: Quickly Start +lang: en-US +--- + +# Quickly Start + +[ORM examples](https://github.com/beego/beego-example/tree/master/orm) + +Beego's ORM is designed as two: + +- Normal `Orm` instances: These instances are stateless, so you should keep a database with only one instance if possible. Of course, even if you create a new instance every time, it's not a big deal, it's just not necessary; +- `TxOrm`: This is the `Orm` object obtained after starting a transaction, it can only be used within the transaction, and will be discarded after commit or rollback, and cannot be reused. A new instance needs to be created for each transaction. + +## Quickly Start + +Exmaple: + +```go +import ( + "github.com/beego/beego/v2/client/orm" + // don't forget this + _ "github.com/go-sql-driver/mysql" +) + +// User - +type User struct { + ID int `orm:"column(id)"` + Name string `orm:"column(name)"` +} + +func init() { + // need to register models in init + orm.RegisterModel(new(User)) + + // need to register default database + orm.RegisterDataBase("default", "mysql", "root:123456@tcp(127.0.0.1:3306)/beego?charset=utf8") +} + +func main() { + // automatically build table + orm.RunSyncdb("default", false, true) + + // create orm object + o := orm.NewOrm() + + // data + user := new(User) + user.Name = "mike" + + // insert data + o.Insert(user) +} + +``` + +In general, it can be divided into the following steps: + +- Define and register models, refer [model](./model.md) +- Register databases, refer [database](./db.md) +- Create `Orm` instances +- Execute queries, Beego provides query API, refer: + - [Orm CRUD](orm.md) + - [QueryBuilder](./query_builder.md) + - [QuerySeter](./query_seter.md) + - [RawSeter](./raw_seter.md) + - [QueryM2Mer](./query_m2m.md) + +It is important to note that you must introduce the driver anonymously according to the database you are using. i.e. `"github.com/go-sql-driver/mysql"` + +## Debug log + +In the development environment, you can output the SQL: + +```go +func main() { + orm.Debug = true +``` + +When turned on, all query statements will be output, including execution, preparation, transactions, etc. Note that this option should not be turned on in production environments, as outputting logs can seriously impact performance. diff --git a/docs/en-US/v2.3.x/orm/cmd.md b/docs/en-US/v2.3.x/orm/cmd.md new file mode 100644 index 0000000..faaef03 --- /dev/null +++ b/docs/en-US/v2.3.x/orm/cmd.md @@ -0,0 +1,64 @@ +--- +title: ORM tool +lang: en-US +--- + +# ORM tool + +After registering [model](. /model.md) and [database](./db.md), call the `RunCommand` method to execute the ORM command。 + +```go +func main() { + // ...registering models + // ...registering database + // don't forget import the driver + orm.RunCommand() +} +``` + +```bash +go build main.go +./main orm +``` + +## Creating table automatically + +```bash +./main orm syncdb -h +Usage of orm command: syncdb: + -db="default": DataBase alias name + -force=false: drop tables before create + -v=false: verbose info +``` +Beego will execute `drop table` before creating tables if you specify the flag `-force=1`. + +And then use `-v` to check the SQL. + +```go +name := "default" + +// drop tables +force := true + +// print SQL +verbose := true + +err := orm.RunSyncdb(name, force, verbose) +if err != nil { + fmt.Println(err) +} +``` +If you do not use the `-force=1` flag, Beego will create new columns or create new indexes. + +But if you wish to update the existing columns or indexes, you need to update them manually. + +> We have received some feedback mentioning that we would like to support deleting fields, or modifying the definition of a field. For now, we are not considering supporting this type of functionality. +> This is mainly from a risk perspective. Compared to adding fields, deleting such operations is much more dangerous and difficult to recover. So we are not very willing to expose this kind of functionality. + +## Print SQL + +```bash +./main orm sqlall -h +Usage of orm command: syncdb: + -db="default": DataBase alias name +``` diff --git a/docs/en-US/v2.3.x/orm/db.md b/docs/en-US/v2.3.x/orm/db.md new file mode 100644 index 0000000..08abc6b --- /dev/null +++ b/docs/en-US/v2.3.x/orm/db.md @@ -0,0 +1,100 @@ +--- +title: Register database +lang: en-US +--- + +# Register database + +Beego ORM requires explicit registration of database information before it can be freely used。 + +And of course, never forget the anonymous introduction of the driver: + +```go +import ( + _ "github.com/go-sql-driver/mysql" + _ "github.com/lib/pq" + _ "github.com/mattn/go-sqlite3" +) +``` + +The above three, you can introduce one according to your needs. + +Example: + +```go +// args[0] Alias of the database, used to switch the database in ORM +// args[1] driverName +// args[2] DSN +orm.RegisterDataBase("default", "mysql", "root:root@/orm_test?charset=utf8") + +// args[3](optional) max number of idle connections +// args[4](optional) max number of connections (go >= 1.2) +maxIdle := 30 +maxConn := 30 +orm.RegisterDataBase("default", "mysql", "root:root@/orm_test?charset=utf8", orm.MaxIdleConnections(maxIdle), orm.MaxOpenConnections(maxConn)) +``` + +ORM requires a `default` database to be registered. And Beego's ORM does not manage connections itself, but relies directly on the driver. + +## Configuration + +### Max number of connections + +There are two ways to set the maximum number of connections, one way is to use the `MaxOpenConnections` option when registering the database: + +```go +orm.RegisterDataBase("default", "mysql", "root:root@/orm_test?charset=utf8", orm.MaxOpenConnections(100)) +``` + +It can also be modified after registration: + +```go +orm.SetMaxOpenConns("default", 30) +``` + +### Max number of idle connections + +There are two ways to set the maximum number of idle connections, one way is to use the `MaxIdleConnections` option when registering the database: +```go +orm.RegisterDataBase("default", "mysql", "root:root@/orm_test?charset=utf8", orm.MaxIdleConnections(20)) +``` + +### Time zone + +ORM uses `time.Local` as default time zone, and you can modify it by: + +```go +// 设置为 UTC 时间 +orm.DefaultTimeLoc = time.UTC +``` + +ORM will get the time zone used by the database while doing `RegisterDataBase`, and then do the corresponding conversion when accessing the `time.Time` type to match the time system, so as to ensure that the time will not be wrong. + +**Notice:** + +- Given the design of Sqlite3, accesses default to UTC time +- When using the go-sql-driver driver, please pay attention to the configuration + From a certain version, the driver uses UTC time by default instead of local time, so please specify the time zone parameter or access it all in UTC time: + For example `root:root@/orm_test?charset=utf8&loc=Asia%2FShanghai` + More details refer [loc](https://github.com/go-sql-driver/mysql#loc) / [parseTime](https://github.com/go-sql-driver/mysql#parsetime) + +## Driver + +Most of the time, you only need to use the default ones for drivers that have: + +```go + DRMySQL // mysql + DRSqlite // sqlite + DROracle // oracle + DRPostgres // pgsql + DRTiDB // TiDB +``` + +If you need to register a custom driver, you can use. + +```go +// args[0] driverName +// args[1] driver implementation +// mysql / sqlite3 / postgres / tidb were registered automatically +orm.RegisterDriver("mysql", yourDriver) +``` diff --git a/docs/en-US/v2.3.x/orm/migrations.md b/docs/en-US/v2.3.x/orm/migrations.md new file mode 100644 index 0000000..956f35f --- /dev/null +++ b/docs/en-US/v2.3.x/orm/migrations.md @@ -0,0 +1,97 @@ +--- +title: Migrations +lang: en-US +--- + +# Migrations + +Models migrations can be generated by using the `bee` command line tool. + +```shell +bee generate migration create_user_table -driver mysql +``` + +Then new migration file will be generated in `database/migrations` folder. + +```go +package main + +import ( + "github.com/beego/beego/v2/client/orm/migration" +) + +// DO NOT MODIFY +type CreateUserTable_20230928_103629 struct { + migration.Migration +} + +// DO NOT MODIFY +func init() { + m := &CreateUserTable_20230928_103629{} + m.Created = "20230928_103629" + + migration.Register("CreateUserTable_20230928_103629", m) +} + +// Run the migrations +func (m *CreateUserTable_20230928_103629) Up() { + // use m.SQL("CREATE TABLE ...") to make schema update + +} + +// Reverse the migrations +func (m *CreateUserTable_20230928_103629) Down() { + // use m.SQL("DROP TABLE ...") to reverse schema update + +} +``` + +Feel `Up()` and `Down()` functions with the next SQL statements. + +```diff +// Run the migrations +func (m *CreateUserTable_20230928_103629) Up() { ++ m.SQL("CREATE TABLE users(id int NOT NULL, name varchar(100) NULL, PRIMARY KEY(id));"); +} + +// Reverse the migrations +func (m *CreateUserTable_20230928_103629) Down() { ++ m.SQL("DROP TABLE users") +} +```` + +Then run the migration, using the `bee` command line tool. + +```shell +bee migrate -driver=mysql -conn="root:pass@tcp(127.0.0.1:3306)/test" -dir="database/migrations" +``` +`users` table will be created in `test` database. + +```shell +describe users; + ++-------+--------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++-------+--------------+------+-----+---------+-------+ +| id | int | NO | PRI | | | +| name | varchar(100) | YES | | | | ++-------+--------------+------+-----+---------+-------+ +``` + + +## Atlas Integration +[Atlas](https://atlasgo.io/) is an open-source database migration tool that has an official integration with Beego. + +While Beego’s [migrations](#migrations) feature works in most cases, +the problem with creating migrations is that the `Up()` and `Down()` functions need to be filled with raw SQL statements, +written by hand, which is error-prone and time consuming. + +Atlas can automatically plan database schema migrations for developers using the official [Beego Provider](https://github.com/ariga/atlas-provider-beego). + +After configuring the provider you can automatically plan migrations by running: + +```shell +atlas migrate diff --env beego +``` + +To learn how to use Atlas with Beego, check out the [official documentation](https://atlasgo.io/guides/orms/beego). diff --git a/docs/en-US/v2.3.x/orm/model.md b/docs/en-US/v2.3.x/orm/model.md new file mode 100644 index 0000000..84f3b9b --- /dev/null +++ b/docs/en-US/v2.3.x/orm/model.md @@ -0,0 +1,508 @@ +--- +title: Model +lang: en-US +--- + +# ORM Model + +Beego's ORM module requires that the model be registered before it can be used, and Beego performs certain checks to assist in checking the model and the constraints between the models。The definition of models has impact on [creating table automatically](./cmd.md) + +Beego's model definition, which mostly relies on Go tag features, can set multiple features, separated by `;`. Different values of the same feature are separated by `,`。 + +Example: + +```go +orm:"null;rel(fk)" +``` + +## Register + +There are three ways to register a model: + +- `RegisterModel(models ...interface{})` +- `RegisterModelWithPrefix(prefix string, models ...interface{})`: This method prefixes the table name with,i.e. `RegisterModelWithPrefix("tab_", &User{})` => `tab_user`; +- `RegisterModelWithSuffix(suffix string, models ...interface{})`: This method adds a suffix to the table name, i.e.`RegisterModelWithSuffix("_tab", &User{})` => `user_tab` + +## Basic Usage + +### Table Name + +Beego ORM will use the snake case as the table name: + +``` +AuthUser -> auth_user +Auth_User -> auth__user +DB_AuthUser -> d_b__auth_user +``` + +Or you can specify the table name by implementing interface `TableNameI`: + +```go +type User struct { + Id int + Name string +} + +func (u *User) TableName() string { + return "auth_user" +} +``` + +Also, you can add a prefix or suffix to the table name when registering the model. More details refer to the section **Registering the Model**。 + +### Column + +Using tag to specify the column name: + +```go +Name string `orm:"column(user_name)"` +``` + +### Ignore Fields + +Using tag "-" to ignore some fields: + +```go +type User struct { + // ... + AnyField string `orm:"-"` + //... +} +``` + +### Index + +Similarly, you can use tag to specify indexes, including unique index. + +For example, specify the index for the field: + +```go +Name string `orm:"index"` +``` + +Or specify the field as unique index: + +```go +Name string `orm:"unique"` +``` + +Or implement the interface `TableIndexI`: + +```go +type User struct { + Id int + Name string + Email string +} + +// index with multiple columns +func (u *User) TableIndex() [][]string { + return [][]string{ + []string{"Id", "Name"}, + } +} + +// unique index with columns +func (u *User) TableUnique() [][]string { + return [][]string{ + []string{"Name", "Email"}, + } +} +``` + +### Primary Key + +You can specify a field as a auto-incrementing primary key using the `auto` tag,and the type of the specific fields must be int, int32, int64, uint, uint32, or uint64。 + +```go +MyId int32 `orm:"auto"` +``` + +If a model does not have a primary key defined, then a field of the above type with the name `Id` will be treated as a auto-incrementing primary key。 + +If you don't want to use a auto-incrementing primary key, then you can use `pk` tag to specify the primary key。 + +```go +Name string `orm:"pk"` +``` + +> Note that Beego's non-auto-incrementing primary keys and union primary keys are not particularly well supported now. It is recommended to use self-incrementing primary keys in general + +Given go's current design, even though uint64 is used, you can't store it to its maximum value. It will still be treated as int64。 More details refer issue [6113](http://code.google.com/p/go/issues/detail?id=6113) + +### Default Value + +you could use it like: + +```go +import ( + "github.com/beego/beego/v2/client/orm/filter/bean" + "github.com/beego/beego/v2/client/orm" +) + +type DefaultValueTestEntity struct { + Id int + Age int `default:"12"` + AgeInOldStyle int `orm:"default(13);bee()"` + AgeIgnore int +} + +func XXX() { + builder := bean.NewDefaultValueFilterChainBuilder(nil, true, true) + orm.AddGlobalFilterChain(builder.FilterChain) + o := orm.NewOrm() + _, _ = o.Insert(&User{ + ID: 1, + Name: "Tom", + }) +} +``` + +`NewDefaultValueFilterChainBuilder`will create an instance of `DefaultValueFilterChainBuilder` +In beego v1.x, the default value config looks like `orm:default(xxxx)` +But the default value in 2.x is `default:xxx`, so if you want to be compatible with v1.x, please pass true as `compatibleWithOldStyle` + +### auto_now / auto_now_add + +```go +Created time.Time `orm:"auto_now_add;type(datetime)"` +Updated time.Time `orm:"auto_now;type(datetime)"` +``` + +* auto_now: every save will update time. +* auto_now_add: set time at the first save + +This setting won't affect massive `update`. + +### engine + +Only supports MySQL database + +The default engine is the default engine of the current database engine of your mysql settings. + +Using `TableEngineI` interface: + +```go +type User struct { + Id int + Name string + Email string +} + +// Set engine to INNODB +func (u *User) TableEngine() string { + return "INNODB" +} +``` + +## Advance Usage + +### null + +Fields are `NOT NULL` by default. Set null to `ALLOW NULL`. + +```go +Name string `orm:"null"` +``` + +### size + +Default value for string field is varchar(255). + +It will use varchar(size) after setting. + +```go +Title string `orm:"size(60)"` +``` +### digits / decimals + +Set precision for float32 or float64. + +```go +Money float64 `orm:"digits(12);decimals(4)"` +``` + +Total 12 digits, 4 digits after point. For example: `12345678.1234` + +### type + +If set type as date, the field's db type is date. + +```go +Created time.Time `orm:"auto_now_add;type(date)"` +``` + +If set type as datetime, the field's db type is datetime. + +```go +Created time.Time `orm:"auto_now_add;type(datetime)"` +``` + +### Time Precision + +```go +type User struct { + ... + Created time.Time `orm:"type(datetime);precision(4)"` + ... +} +``` + +### Comment + +```go +type User struct { + ... + Status int `orm:"default(1);description(this is status)"` + ... +} +``` + +You should never use quoter as the value of description. + +## Types Mapping + +Model fields mapping with database type + +Here is the recommended database type mapping. It's also the standard for table generation. + +All the fields are **NOT NULL** by default. + +### MySQL + +| go |mysql +| :--- | :--- +| int, int32 - set as auto or name is `Id` | integer AUTO_INCREMENT +| int64 - set as auto or name is`Id` | bigint AUTO_INCREMENT +| uint, uint32 - set as auto or name is `Id` | integer unsigned AUTO_INCREMENT +| uint64 - set as auto or name is `Id` | bigint unsigned AUTO_INCREMENT +| bool | bool +| string - default size 255 | varchar(size) +| string - set type(char) | char(size) +| string - set type(text) | longtext +| time.Time - set type as date | date +| time.Time | datetime +| byte | tinyint unsigned +| rune | integer +| int | integer +| int8 | tinyint +| int16 | smallint +| int32 | integer +| int64 | bigint +| uint | integer unsigned +| uint8 | tinyint unsigned +| uint16 | smallint unsigned +| uint32 | integer unsigned +| uint64 | bigint unsigned +| float32 | double precision +| float64 | double precision +| float64 - set digits and decimals | numeric(digits, decimals) + +### Sqlite3 + +| go | sqlite3 +| :--- | :--- +| int, int32, int64, uint, uint32, uint64 - set as auto or name is `Id` | integer AUTOINCREMENT +| bool | bool +| string - default size 255 | varchar(size) +| string - set type(char) | character(size) +| string - set type(text) | text +| time.Time - set type as date | date +| time.Time | datetime +| byte | tinyint unsigned +| rune | integer +| int | integer +| int8 | tinyint +| int16 | smallint +| int32 | integer +| int64 | bigint +| uint | integer unsigned +| uint8 | tinyint unsigned +| uint16 | smallint unsigned +| uint32 | integer unsigned +| uint64 | bigint unsigned +| float32 | real +| float64 | real +| float64 - set digits and decimals | decimal + +### PostgreSQL + +| go | postgres +| :--- | :--- +| int, int32, int64, uint, uint32, uint64 - set as auto or name is `Id` | serial +| bool | bool +| string - if not set size default text | varchar(size) +| string - set type(char) | char(size) +| string - set type(text) | text +| string - set type(json) | json +| string - set type(jsonb) | jsonb +| time.Time - set type as date | date +| time.Time | timestamp with time zone +| byte | smallint CHECK("column" >= 0 AND "column" <= 255) +| rune | integer +| int | integer +| int8 | smallint CHECK("column" >= -127 AND "column" <= 128) +| int16 | smallint +| int32 | integer +| int64 | bigint +| uint | bigint CHECK("column" >= 0) +| uint8 | smallint CHECK("column" >= 0 AND "column" <= 255) +| uint16 | integer CHECK("column" >= 0) +| uint32 | bigint CHECK("column" >= 0) +| uint64 | bigint CHECK("column" >= 0) +| float32 | double precision +| float64 | double precision +| float64 - set digits and decimals | numeric(digits, decimals) + +## Relationships + +### rel / reverse + +**RelOneToOne**: + +```go +type User struct { + ... + Profile *Profile `orm:"null;rel(one);on_delete(set_null)"` + ... +} +``` + +The reverse relationship **RelReverseOne**: + +```go +type Profile struct { + ... + User *User `orm:"reverse(one)"` + ... +} +``` + +**RelForeignKey**: + +```go +type Post struct { + ... + User *User `orm:"rel(fk)"` // RelForeignKey relation + ... +} +``` + +The reverse relationship **RelReverseMany**: + +```go +type User struct { + ... + Posts []*Post `orm:"reverse(many)"` // fk 的反向关系 + ... +} +``` + +**RelManyToMany**: + +```go +type Post struct { + ... + Tags []*Tag `orm:"rel(m2m)"` // ManyToMany relation + ... +} +``` + +The reverse relationship **RelReverseMany**: + +```go +type Tag struct { + ... + Posts []*Post `orm:"reverse(many)"` + ... +} +``` + +### rel_table / rel_through + +This setting is for `orm:"rel(m2m)"` field: + + rel_table Set the auto-generated m2m connecting table name + rel_through If you want to use custom m2m connecting table, set name by using this setting. + Format: `project_path/current_package.ModelName` + For example: `app/models.PostTagRel` PostTagRel table needs to have a relationship to Post table and Tag table. + + +If rel_table is set, rel_through is ignored. + +You can set these as follows: + +`orm:"rel(m2m);rel_table(the_table_name)"` + +`orm:"rel(m2m);rel_through(project_path/current_package.ModelName)"` + +### on_delete + +Set how to deal with field if related relationship is deleted: + + cascade cascade delete (default) + set_null set to NULL. Need to set null = true + set_default set to default value. Need to set default value. + do_nothing do nothing. ignore. + +```go +type User struct { + ... + Profile *Profile `orm:"null;rel(one);on_delete(set_null)"` + ... +} +type Profile struct { + ... + User *User `orm:"reverse(one)"` + ... +} + +// Set User.Profile to NULL while deleting Profile +``` + +#### Exmaple + +```go +type User struct { + Id int + Name string +} + +type Post struct { + Id int + Title string + User *User `orm:"rel(fk)"` +} +``` + +Assume Post -> User is ManyToOne relationship by foreign key. + +``` +o.Filter("Id", 1).Delete() +``` + +This will delete User with Id 1 and all his Posts. + +If you don't want to delete the Posts, you need to set `set_null` + +```go +type Post struct { + Id int + Title string + User *User `orm:"rel(fk);null;on_delete(set_null)"` +} +``` + +In this case, only set related Post.user_id to NULL while deleting. + +Usually for performance purposes, it doesn't matter to have redundant data. The massive deletion is the real problem + +```go +type Post struct { + Id int + Title string + User *User `orm:"rel(fk);null;on_delete(do_nothing)"` +} +``` + +So just don't change Post (ignore it) while deleting User. diff --git a/docs/en-US/v2.3.x/orm/orm.md b/docs/en-US/v2.3.x/orm/orm.md new file mode 100644 index 0000000..68e4ec4 --- /dev/null +++ b/docs/en-US/v2.3.x/orm/orm.md @@ -0,0 +1,208 @@ +--- +title: ORM CRUD +lang: en-US +--- + +# ORM CRUD + +A simple `Orm` instance can be created as follows: + +```go +var o orm.Ormer +o = orm.NewOrm() // create Ormer +// NewOrm will execute orm.BootStrap once which will verify the models' definitions. +``` + +In most cases, you should try to reuse `Orm` instances, as they are designed to be stateless, with one database corresponding to one `Orm` instance + + +But when using transactions, we return instances of `TxOrm`, which is itself stateful, with one transaction corresponding to one instance of `TxOrm`. When using `TxOrm`, any derived queries are within that transaction. + +## Insert And InsertWithCtx + +API: + +```go +Insert(md interface{}) (int64, error) +InsertWithCtx(ctx context.Context, md interface{}) (int64, error) +``` + +Example: + +```go +user := new(User) +id, err = Ormer.Insert(user) +``` + +You should use the pointer as input. + +## InsertOrUpdate And InsertOrUpdateWithCtx + +API: + +```go +InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) +InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) +``` + +These two methods have different effects under different dialects: + +- For MySQL, it means `ON DUPLICATE KEY`. So you don't need to pass the `colConflictAndArgs`; +- For PostgreSQL and sqlite, it means `ON CONFLICT cols DO UPDATE SET`, so you can specify the columns by passing `colConflictAndArgs`; +- For other dialects, you need to confirm if they have similar features; + +## InsertMulti And InsertMultiWithCtx + +Insert multiple objects in one api. + +Like sql statement: + +```sql +insert into table (name, age) values("slene", 28),("astaxie", 30),("unknown", 20) +``` + +The 1st param is the number of records to insert in one bulk statement. The 2nd param is models slice. + +The return value is the number of successfully inserted rows. + +```go +users := []User{ + {Name: "slene"}, + {Name: "astaxie"}, + {Name: "unknown"}, + ... +} +successNums, err := o.InsertMulti(100, users) +``` + +## Update And UpdateWithCtx + +Beego uses the primary key to generate the WHERE clause by default. + +API: + +```go +Update(md interface{}, cols ...string) (int64, error) +UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) +``` + +All columns will be updated if you do not specify columns. + +The first return value is the number of affected rows. + +## Delete And DeleteWithCtx + +Beego uses the primary key to generate the WHERE clause: + +```go +Delete(md interface{}, cols ...string) (int64, error) +DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) +``` + +The first return value is the number of affected rows. + +## Read And ReadWithCtx + +API: + +```go +Read(md interface{}, cols ...string) error +ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error +``` + +Notice: + +- The returned rows will be used to initiate `md`; +- It only fetches the columns specified by `cols`; + +Example: + +```go +// Read all columns +u = &User{Id: user.Id} +err = Ormer.Read(u) + +// Read column `UserName` only +u = &User{} +err = Ormer.Read(u, "UserName") +``` + +## ReadForUpdate And ReadForUpdateWithCtx + +API: + +```go +ReadForUpdate(md interface{}, cols ...string) error +ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error +``` + +They are similar to `Read` and `ReadWithCtx`. The difference is that these two methods add FOR UPDATE to the query and are therefore commonly used within transactions + +However, not all databases support the FOR UPDATE statement, so you should first make sure your database supports the FOR UPDATE usage when you use it. + +## ReadOrCreate And ReadOrCreateWithCtx + +API: + +```go +ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) +ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) +``` + +Find data from the database, and if it does not exist, then insert. + +Note that the "find-judge-insert" action is not atomic or thread-safe. Therefore, in a concurrent environment, it may behave in a way that exceeds your expectations. For example, if two goroutines determine that data does not exist, they will both try to insert. + +## Raw And RawWithContext + +```go + Raw(query string, args ...interface{}) RawSeter + RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter +``` + +Beego does not support all SQL syntax features, so in some special cases, you need to use raw queries. + +They return `RawSeter`, and more details refer [RawSeter](./raw_seter.md)。 + +## LoadRelated And LoadRelatedWithCtx + +API: + +```go +LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) +LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) +``` + +`LoadRelatedWithCtx` was deprecated. + +These two methods are used to load data from related tables, such as: + +```go +o.LoadRelated(post,"Tags") +for _,tag := range post.Tags{ + // your business code +} +``` + +Notice that the last parameter of both methods is passed in the KV values, which are currently defined inside the `hints` package, with: + +- `hints.DefaultRelDepth`: Set the resolution depth of the associated table to the default value of 2; +- `hints.RelDepth` +- `hints.Limit` +- `hints.Offset` +- `hints.OrderBy` + +This method should be used with caution, especially if the offset or depth is set to a large value, the response time will be longer. + +## QueryM2M And QueryM2MWithCtx + +API: + +```go + QueryM2M(md interface{}, name string) QueryM2Mer + QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer +``` + +`QueryM2MWithCtx` was deprecated, as the `ctx` parameter has no effect. + +More details refer [QueryM2Mer](./query_m2m.md#) diff --git a/docs/en-US/v2.3.x/orm/query_builder.md b/docs/en-US/v2.3.x/orm/query_builder.md new file mode 100644 index 0000000..aab6785 --- /dev/null +++ b/docs/en-US/v2.3.x/orm/query_builder.md @@ -0,0 +1,75 @@ +--- +title: QueryBuilder +lang: en-US +--- + +# QueryBuilder Constructs Complicate Query + +**QueryBuilder** provides an API for convenient and fluent construction of SQL queries. It consists of a set of methods enabling developers to easily construct SQL queries without compromising readability. + +It serves as an alternative to ORM. ORM is more for simple CRUD operations, whereas QueryBuilder is for complex queries with subqueries and multi-joins. + +Usage example: + +```go +// User is a wrapper for result row in this example +type User struct { + Name string + Age int +} +var users []User + +// Get a QueryBuilder object. Takes DB driver name as parameter +// Second return value is error, ignored here +qb, _ := orm.NewQueryBuilder("mysql") + +// Construct query object +qb.Select("user.name", + "profile.age"). + From("user"). + InnerJoin("profile").On("user.id_user = profile.fk_user"). + Where("age > ?"). + OrderBy("name").Desc(). + Limit(10).Offset(0) + +// export raw query string from QueryBuilder object +sql := qb.String() + +// execute the raw query string +o := orm.NewOrm() +o.Raw(sql, 20).QueryRows(&users) +``` + +Full API interface: + +```go +type QueryBuilder interface { + Select(fields ...string) QueryBuilder + ForUpdate() QueryBuilder + From(tables ...string) QueryBuilder + InnerJoin(table string) QueryBuilder + LeftJoin(table string) QueryBuilder + RightJoin(table string) QueryBuilder + On(cond string) QueryBuilder + Where(cond string) QueryBuilder + And(cond string) QueryBuilder + Or(cond string) QueryBuilder + In(vals ...string) QueryBuilder + OrderBy(fields ...string) QueryBuilder + Asc() QueryBuilder + Desc() QueryBuilder + Limit(limit int) QueryBuilder + Offset(offset int) QueryBuilder + GroupBy(fields ...string) QueryBuilder + Having(cond string) QueryBuilder + Update(tables ...string) QueryBuilder + Set(kv ...string) QueryBuilder + Delete(tables ...string) QueryBuilder + InsertInto(table string, fields ...string) QueryBuilder + Values(vals ...string) QueryBuilder + Subquery(sub string, alias string) string + String() string +} +``` + +Now we support `Postgress`, `MySQL` and `TiDB`. \ No newline at end of file diff --git a/docs/en-US/v2.3.x/orm/query_m2m.md b/docs/en-US/v2.3.x/orm/query_m2m.md new file mode 100644 index 0000000..7145d37 --- /dev/null +++ b/docs/en-US/v2.3.x/orm/query_m2m.md @@ -0,0 +1,105 @@ +--- +title: Relationships +lang: en-US +--- + +# Relationships + +For querying relationships, you can use [QuerySeter](./query_seter.md) or `QueryM2Mer`。 + +For example: + +```go +o := orm.NewOrm() +post := Post{Id: 1} +m2m := o.QueryM2M(&post, "Tags") +// In the first param object must have primary key +// The second param is the M2M field will work with +// API of QueryM2Mer will used to Post with id equals 1 +``` + +Full API: + +- [Add(...interface{}) (int64, error)](#querym2mer-add) +- [Remove(...interface{}) (int64, error)](#querym2mer-remove) +- [Exist(interface{}) bool](#querym2mer-exist) +- [Clear() (int64, error)](#querym2mer-clear) +- [Count() (int64, error)](#querym2mer-count) + +## QueryM2Mer Add + +```go +tag := &Tag{Name: "golang"} +o.Insert(tag) + +num, err := m2m.Add(tag) +if err == nil { + fmt.Println("Added nums: ", num) +} +``` + +`Add` accepts `Tag`,`*Tag`,`[]*Tag`,`[]Tag`,`[]interface{}`。 + +```go +var tags []*Tag +... +// After reading tags +... +num, err := m2m.Add(tags) +if err == nil { + fmt.Println("Added nums: ", num) +} +// It can pass multiple params +// m2m.Add(tag1, tag2, tag3) +``` + +## QueryM2Mer Remove + +Remove tag from M2M relation: + +Remove supports many types: Tag *Tag []*Tag []Tag []interface{} + +```go +var tags []*Tag +... +// After reading tags +... +num, err := m2m.Remove(tags) +if err == nil { + fmt.Println("Removed nums: ", num) +} +// It can pass multiple params +// m2m.Remove(tag1, tag2, tag3) +``` + +## QueryM2Mer Exist + +Test if Tag is in M2M relation + +```go +if m2m.Exist(&Tag{Id: 2}) { + fmt.Println("Tag Exist") +} +``` + +## QueryM2Mer Clear + +Clear all M2M relation: + +```go +nums, err := m2m.Clear() +if err == nil { + fmt.Println("Removed Tag Nums: ", nums) +} +``` + +## QueryM2Mer Count + +Count the number of Tags: + +```go +nums, err := m2m.Count() +if err == nil { + fmt.Println("Total Nums: ", nums) +} +``` diff --git a/docs/en-US/v2.3.x/orm/query_seter.md b/docs/en-US/v2.3.x/orm/query_seter.md new file mode 100644 index 0000000..fe78936 --- /dev/null +++ b/docs/en-US/v2.3.x/orm/query_seter.md @@ -0,0 +1,636 @@ +--- +title: QuerySeter +lang: en-US +--- + +# QuerySeter + +ORM uses **QuerySeter** to organize queries. Every method that returns **QuerySeter** will give you a new **QuerySeter** object. + +Basic Usage: + +```go +o := orm.NewOrm() + +// or +qs := o.QueryTable("user") + +// or +qs = o.QueryTable(&User) + +// or +user := new(User) +qs = o.QueryTable(user) // return QuerySeter + +``` + +The methods of `QuerySeter` can be roughly divided into two categories: + +- Intermediate methods: used to construct the query +- Terminate methods: used to execute the query and encapsulate the result + +* Each api call that returns a QuerySeter creates a new QuerySeter, without affecting the previously created. + +* Advanced queries use Filter and Exclude to do common conditional queries. + +## Query Expression + +Beego has designed its own query expressions, which can be used in many methods. + +In general, you can use expressions for fields in a single table, or you can use expressions on related tables. For example: +```go +qs.Filter("id", 1) // WHERE id = 1 +``` + +Or in relationships: + +```go +qs.Filter("profile__age", 18) // WHERE profile.age = 18 +qs.Filter("Profile__Age", 18) // key name and field name are both valid +qs.Filter("profile__age", 18) // WHERE profile.age = 18 +qs.Filter("profile__age__gt", 18) // WHERE profile.age > 18 +qs.Filter("profile__age__gte", 18) // WHERE profile.age >= 18 +qs.Filter("profile__age__in", 18, 20) // WHERE profile.age IN (18, 20) + +qs.Filter("profile__age__in", 18, 20).Exclude("profile__lt", 1000) +// WHERE profile.age IN (18, 20) AND NOT profile_id < 1000 +``` + +For example, if the User table has a foreign key for Profile, then if the User table is queried for the corresponding Profile.Age, then `Profile__Age` is used. Note that the field separators use the double underscore `__` for the field separator. + +Operators can be added to the end of an expression to perform the corresponding sql operation. For example, `Profile__Age__gt` represents a conditional query for Profile.Age > 18. If no operator is specified, `=` will be used as the operator. + +The supported operators: + +* [exact](#exact) / [iexact](#iexact) equal to +* [contains](#contains) / [icontains](#icontains) contains +* [gt / gte](#gt / gte) greater than / greater than or equal to +* [lt / lte](#lt / lte) less than / less than or equal to +* [startswith](#startswith) / [istartswith](#istartswith) starts with +* [endswith](#endswith) / [iendswith](#iendswith) ends with +* [in](#in) +* [isnull](#isnull) + +The operators that start with `i` ignore case. + +### exact + +Default values of Filter, Exclude and Condition expr + +```go +qs.Filter("name", "slene") // WHERE name = 'slene' +qs.Filter("name__exact", "slene") // WHERE name = 'slene' +// using = , case sensitive or not is depending on which collation database table is used +qs.Filter("profile", nil) // WHERE profile_id IS NULL +``` + +### iexact + +```go +qs.Filter("name__iexact", "slene") +// WHERE name LIKE 'slene' +// Case insensitive, will match any name that equals to 'slene' +``` + +### contains + +```go +qs.Filter("name__contains", "slene") +// WHERE name LIKE BINARY '%slene%' +// Case sensitive, only match name that contains 'slene' +``` + +### icontains + +```go +qs.Filter("name__icontains", "slene") +// WHERE name LIKE '%slene%' +// Case insensitive, will match any name that contains 'slene' +``` + +### in + +```go +qs.Filter("profile__age__in", 17, 18, 19, 20) +// WHERE profile.age IN (17, 18, 19, 20) +``` + +### gt / gte + +```go +qs.Filter("profile__age__gt", 17) +// WHERE profile.age > 17 + +qs.Filter("profile__age__gte", 18) +// WHERE profile.age >= 18 +``` + +### lt / lte + +```go +qs.Filter("profile__age__lt", 17) +// WHERE profile.age < 17 + +qs.Filter("profile__age__lte", 18) +// WHERE profile.age <= 18 +``` + +### startswith + +```go +qs.Filter("name__startswith", "slene") +// WHERE name LIKE BINARY 'slene%' +// Case sensitive, only match name that starts with 'slene' +``` + +### istartswith + +```go +qs.Filter("name__istartswith", "slene") +// WHERE name LIKE 'slene%' +// Case insensitive, will match any name that starts with 'slene' +``` + +### endswith + +```go +qs.Filter("name__endswith", "slene") +// WHERE name LIKE BINARY '%slene' +// Case sensitive, only match name that ends with 'slene' +``` + +### iendswith + +```go +qs.Filter("name__iendswith", "slene") +// WHERE name LIKE '%slene' +// Case insensitive, will match any name that ends with 'slene' +``` + +### isnull + +```go +qs.Filter("profile__isnull", true) +qs.Filter("profile_id__isnull", true) +// WHERE profile_id IS NULL + +qs.Filter("profile__isnull", false) +// WHERE profile_id IS NOT NULL +``` + +## Intermediate Methods + +### Filter + +Used to filter the result for the **include conditions**. + +Use `AND` to connect multiple filters: + +```go +qs.Filter("profile__isnull", true).Filter("name", "slene") +// WHERE profile_id IS NULL AND name = 'slene' +``` + +### FilterRaw + +```go +FilterRaw(string, string) QuerySeter +``` + +This method treats the input directly as a query condition, so if there is an error in the input, then the resulting spliced SQL will not work. Beego itself does not perform any checks. + +例如: + +```go +qs.FilterRaw("user_id IN (SELECT id FROM profile WHERE age>=18)") +//sql-> WHERE user_id IN (SELECT id FROM profile WHERE age>=18) +``` + +### Exclude + +Used to filter the result for the **exclude conditions**. + +Use `NOT` to exclude condition +Use `AND` to connect multiple filters: + +```go +qs.Exclude("profile__isnull", true).Filter("name", "slene") +// WHERE NOT profile_id IS NULL AND name = 'slene' +``` + + +### SetCond + +Custom conditions: + +```go +cond := NewCondition() +cond1 := cond.And("profile__isnull", false).AndNot("status__in", 1).Or("profile__age__gt", 2000) + +qs := orm.QueryTable("user") +qs = qs.SetCond(cond1) +// WHERE ... AND ... AND NOT ... OR ... + +cond2 := cond.AndCond(cond1).OrCond(cond.And("name", "slene")) +qs = qs.SetCond(cond2).Count() +// WHERE (... AND ... AND NOT ... OR ...) OR ( ... ) +``` + +### GetCond + +```go +GetCond() *Condition +``` + +It returns all conditions: + +```go + cond := orm.NewCondition() + cond = cond.And("profile__isnull", false).AndNot("status__in", 1) + qs = qs.SetCond(cond) + cond = qs.GetCond() + cond := cond.Or("profile__age__gt", 2000) + //sql-> WHERE T0.`profile_id` IS NOT NULL AND NOT T0.`Status` IN (?) OR T1.`age` > 2000 + num, err := qs.SetCond(cond).Count() +``` + +### Limit + +Limit maximum returned lines. The second param can set `Offset` + +```go +var DefaultRowsLimit = 1000 // The default limit of ORM is 1000 + +// LIMIT 1000 + +qs.Limit(10) +// LIMIT 10 + +qs.Limit(10, 20) +// LIMIT 10 OFFSET 20 + +qs.Limit(-1) +// no limit + +qs.Limit(-1, 100) +// LIMIT 18446744073709551615 OFFSET 100 +// 18446744073709551615 is 1<<64 - 1. Used to set the condition which is no limit but with offset +``` + +If you do not call the method, or if you call the method but pass in a negative number, Beego will use the default value, e.g. 1000. + +### Offset + +Set offset lines: + +```go +qs.Offset(20) +// LIMIT 1000 OFFSET 20 +``` +### GroupBy + +```go +qs.GroupBy("id", "age") +// GROUP BY id,age +``` + +### OrderBy + +```go +OrderBy(exprs ...string) QuerySeter +``` + +Cases: + +- If the column names are passed in, then it means sort by column name ASC; +- If the column names with symbol `-` are passed in, then it means sort by column name DESC; + +Example: + +```go +qs.OrderBy("id", "-profile__age") +// ORDER BY id ASC, profile.age DESC + +qs.OrderBy("-profile__age", "profile") +// ORDER BY profile.age DESC, profile_id ASC +``` + +Similarly: + +```go +qs.OrderBy("id", "-profile__age") +// ORDER BY id ASC, profile.age DESC + +qs.OrderBy("-profile__age", "profile") +// ORDER BY profile.age DESC, profile_id ASC +``` + +### ForceIndex + +Forcing DB to use the index. + +You need to check your DB whether it support this feature. + +```go +qs.ForceIndex(`idx_name1`,`idx_name2`) +``` + +### UseIndex + +Suggest DB to user the index. + +You need to check your DB whether it support this feature. + +```go +qs.UseIndex(`idx_name1`,`idx_name2`) +``` + +### IgnoreIndex + +Make DB ignore the index + +You need to check your DB whether it support this feature. + +```go +qs.IgnoreIndex(`idx_name1`,`idx_name2`) +``` + +### RelatedSel + +```go +RelatedSel(params ...interface{}) QuerySeter +``` + +Loads the data of the associated table. If no parameters are passed, then Beego loads the data of all related tables. If parameters are passed, then only the specific table data is loaded. + +When loading, if the corresponding field is available as NULL, then LEFT JOIN is used, otherwise JOIN is used. + +Example: + +```go +// Use LEFT JOIN to load all the related table data of table user +qs.RelatedSel().One(&user) +// Use LEFT JOIN to load only the data of the profile of table user +qs.RelatedSel("profile").One(&user) +user.Profile.Age = 32 +``` + +Calling RelatedSel directly by default will perform a relational query at the maximum `DefaultRelsDepth`. + +### Distinct + +Same as `distinct` statement in sql, return only distinct (different) values + +```go +qs.Distinct() +// SELECT DISTINCT +``` + +### ForUpdate + +```go +ForUpdate() QuerySeter +``` + +Add FOR UPDATE clause. + +### PrepareInsert + +```go +PrepareInsert() (Inserter, error) +``` + +Used to prepare multiple insert inserts at once to increase the speed of bulk insertion. + +```go +var users []*User +... +qs := o.QueryTable("user") +i, _ := qs.PrepareInsert() +for _, user := range users { + id, err := i.Insert(user) + if err == nil { + ... + } +} +// PREPARE INSERT INTO user (`name`, ...) VALUES (?, ...) +// EXECUTE INSERT INTO user (`name`, ...) VALUES ("slene", ...) +// EXECUTE ... +// ... +i.Close() // don't forget to close statement +``` + +### Aggregate + +```go +Aggregate(s string) QuerySeter +``` + +Using aggregate functions: + +```go +type result struct { + DeptName string + Total int +} +var res []result +o.QueryTable("dept_info").Aggregate("dept_name,sum(salary) as total").GroupBy("dept_name").All(&res) +``` + +## Terminate Methods + +### Count + +```go +Count() (int64, error) +``` + +Return line count based on the current query + +### Exist + +```go +Exist() bool +``` + +Determines if the query returns data. Equivalent to `Count()` to return a value greater than 0。 + +### Update + +Execute batch updating based on the current query + +```go +num, err := o.QueryTable("user").Filter("name", "slene").Update(orm.Params{ + "name": "astaxie", +}) +fmt.Printf("Affected Num: %s, %s", num, err) +// SET name = "astaixe" WHERE name = "slene" +``` + +Atom operation add field: + +```go +// Assume there is a nums int field in user struct +num, err := o.QueryTable("user").Update(orm.Params{ + "nums": orm.ColValue(orm.Col_Add, 100), +}) +// SET nums = nums + 100 +``` + +orm.ColValue supports: + +```go +Col_Add // plus +Col_Minus // minus +Col_Multiply // multiply +Col_Except // divide +``` + +### Delete + +```go +Delete() (int64, error) +``` + +Execute batch deletion based on the current query. + +### All + +Return the related ResultSet + +Param of `All` supports *[]Type and *[]*Type + +```go +var users []*User +num, err := o.QueryTable("user").Filter("name", "slene").All(&users) +fmt.Printf("Returned Rows Num: %s, %s", num, err) +``` + +All / Values / ValuesList / ValuesFlat will be limited by [Limit](#limit). 1000 lines by default. + +The returned fields can be specified: + +```go +type Post struct { + Id int + Title string + Content string + Status int +} + +// Only return Id and Title +var posts []Post +o.QueryTable("post").Filter("Status", 1).All(&posts, "Id", "Title") +``` + +The other fields of the object are set to the default value of the field's type. + +### One + +Try to return one record + +```go +var user User +err := o.QueryTable("user").Filter("name", "slene").One(&user) +if err == orm.ErrMultiRows { + // Have multiple records + fmt.Printf("Returned Multi Rows Not One") +} +if err == orm.ErrNoRows { + // No result + fmt.Printf("Not row found") +} +``` + +The returned fields can be specified: + +```go +// Only return Id and Title +var post Post +o.QueryTable("post").Filter("Content__istartswith", "prefix string").One(&post, "Id", "Title") +``` + +The other fields of the object are set to the default value of the fields' type. +### Values + +Return key => value of result set + +key is Field name in Model. value type if string. + +```go +var maps []orm.Params +num, err := o.QueryTable("user").Values(&maps) +if err == nil { + fmt.Printf("Result Nums: %d\n", num) + for _, m := range maps { + fmt.Println(m["Id"], m["Name"]) + } +} +``` + +Return specific fields: + +**TODO**: doesn't support recursive query. **RelatedSel** return Values directly + +But it can specify the value needed by expr. + +```go +var maps []orm.Params +num, err := o.QueryTable("user").Values(&maps, "id", "name", "profile", "profile__age") +if err == nil { +fmt.Printf("Result Nums: %d\n", num) +for _, m := range maps { +fmt.Println(m["Id"], m["Name"], m["Profile"], m["Profile__Age"]) +// There is no complicated nesting data in the map +} +} +``` + +### ValuesList + +The result set will be stored as a slice + +The order of the result is same as the Fields order in the Model definition. + +The values are saved as strings. + +```go +var lists []orm.ParamsList +num, err := o.QueryTable("user").ValuesList(&lists) +if err == nil { + fmt.Printf("Result Nums: %d\n", num) + for _, row := range lists { + fmt.Println(row) + } +} +``` + +It can return specific fields by setting expr. + +```go +var lists []orm.ParamsList +num, err := o.QueryTable("user").ValuesList(&lists, "name", "profile__age") +if err == nil { + fmt.Printf("Result Nums: %d\n", num) + for _, row := range lists { + fmt.Printf("Name: %s, Age: %s\m", row[0], row[1]) + } +} +``` + +### ValuesFlat + +Only returns a single values slice of a specific field. + +```go +var list orm.ParamsList +num, err := o.QueryTable("user").ValuesFlat(&list, "name") +if err == nil { + fmt.Printf("Result Nums: %d\n", num) + fmt.Printf("All User Names: %s", strings.Join(list, ", ")) +} +``` + +### RowsToMap 和 RowsToStruct + +Not implement. diff --git a/docs/en-US/v2.3.x/orm/raw_seter.md b/docs/en-US/v2.3.x/orm/raw_seter.md new file mode 100644 index 0000000..4638603 --- /dev/null +++ b/docs/en-US/v2.3.x/orm/raw_seter.md @@ -0,0 +1,174 @@ +--- +title: Raw Query +lang: en-US +--- + +# Raw Query + +Most of the time, you should not use raw queries. Raw queries should only be considered when there is no other choice. + +* Using Raw SQL to query doesn't require an ORM definition +* Multiple databases support `?` as placeholders and auto convert. +* The params of query support Model Struct, Slice and Array + +Example: + +```go +o := orm.NewOrm() +ids := []int{1, 2, 3} +var r RawSter +r = o.Raw("SELECT name FROM user WHERE id IN (?, ?, ?)", ids) +``` + +## Exec + +Run sql query and return [sql.Result](http://gowalker.org/database/sql#Result) object + +```go +res, err := o.Raw("UPDATE user SET name = ?", "your").Exec() +if err == nil { +num, _ := res.RowsAffected() +fmt.Println("mysql row affected nums: ", num) +} +``` + +## QueryRow And QueryRows + +API: + +```go +QueryRow(containers ...interface{}) error +QueryRows(containers ...interface{}) (int64, error) +``` + +They will use the returned values to initiate `container`。 + +Example: + +```go +var name string +var id int +// id==2 name=="slene" +dORM.Raw("SELECT 'id','name' FROM `user`").QueryRow(&id,&name) +``` + +In this example, `QueryRow` will query to get two columns and only one row. In this case, the values of the two columns are assigned to `id` and `name` respectively. + +QueryRows Example: + +```go +var ids []int +var names []int +query = "SELECT 'id','name' FROM `user`" +// ids=>{1,2},names=>{"nobody","slene"} +num, err = dORM.Raw(query).QueryRows(&ids,&names) +``` + +Similarly, `QueryRows` is also returned by column, so you can notice that in the example we have declared two slices corresponding to the columns `id` and `name` respectively。 + +## SetArgs + +Changing args param in Raw(sql, args...) can return a new RawSeter: + +```go +SetArgs(...interface{}) RawSeter +``` + +Example: + +```go +var name string +var id int +query := "SELECT 'id','name' FROM `user` WHERE `id`=?" +// id==2 name=="slene" +// 等效于"SELECT 'id','name' FROM `user` WHERE `id`=1" +dORM.Raw(query).SetArgs(1).QueryRow(&id,&name) +``` + +It can also be used in a single sql statement, reused, replacing parameters and then executed. + +```go +res, err := r.SetArgs("arg1", "arg2").Exec() +res, err := r.SetArgs("arg1", "arg2").Exec() + +``` + +## Values / ValuesList / ValuesFlat + +```go + Values(container *[]Params, cols ...string) (int64, error) + ValuesList(container *[]ParamsList, cols ...string) (int64, error) + ValuesFlat(container *ParamsList, cols ...string) (int64, error) +``` + +More details refer: + +- [Values](./query_seter.md#values) +- [ValuesList](./query_seter.md#valueslist) +- [ValuesFlat](./query_seter.md#valuesflat) + +## RowsToMap + +```go +RowsToMap(result *Params, keyCol, valueCol string) (int64, error) +``` + +SQL query results + +| name | value | +| --- | --- | +| total | 100 | +| found | 200 | + +map rows results to map + +```go +res := make(orm.Params) +nums, err := o.Raw("SELECT name, value FROM options_table").RowsToMap(&res, "name", "value") +// res is a map[string]interface{}{ +// "total": 100, +// "found": 200, +// } +``` + +## RowsToStruct + +```go +RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error) +``` + +SQL query results + +| name | value | +| --- | --- | +| total | 100 | +| found | 200 | + +map rows results to struct + +```go +type Options struct { + Total int + Found int +} + +res := new(Options) +nums, err := o.Raw("SELECT name, value FROM options_table").RowsToStruct(res, "name", "value") +fmt.Println(res.Total) // 100 +fmt.Println(res.Found) // 200 +``` + +> support name conversion: snake -> camel, eg: SELECT user_name ... to your struct field UserName. + +## Prepare + +Prepare once and exec multiple times to improve the speed of batch execution. + +```go +p, err := o.Raw("UPDATE user SET name = ? WHERE name = ?").Prepare() +res, err := p.Exec("testing", "slene") +res, err = p.Exec("testing", "astaxie") +... +... +p.Close() // Don't forget to close the prepare statement. +``` diff --git a/docs/en-US/v2.3.x/orm/transaction.md b/docs/en-US/v2.3.x/orm/transaction.md new file mode 100644 index 0000000..57e1e2f --- /dev/null +++ b/docs/en-US/v2.3.x/orm/transaction.md @@ -0,0 +1,97 @@ +--- +title: Transaction +lang: en-US +--- + +# Transaction + +More API refer [Orm 增删改查](./orm.md) + +There are two ways to handle transaction in Beego. One is closure: + +```go + // Beego will manage the transaction's lifecycle + // if the @param task return error, the transaction will be rollback + // or the transaction will be committed + err := o.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error { + // data + user := new(User) + user.Name = "test_transaction" + + // insert data + // Using txOrm to execute SQL + _, e := txOrm.Insert(user) + // if e != nil the transaction will be rollback + // or it will be committed + return e + }) +``` +In this way, the first parameter is `task`, all DB operation should be inside the task. + +If the task return error, Beego rollback the transaction. + +We recommend you to use this way. + +Another way is that users handle transaction manually: + +```go + o := orm.NewOrm() + to, err := o.Begin() + if err != nil { + logs.Error("start the transaction failed") + return + } + + user := new(User) + user.Name = "test_transaction" + + // do something with to. to is an instance of TxOrm + + // insert data + // Using txOrm to execute SQL + _, err = to.Insert(user) + + if err != nil { + logs.Error("execute transaction's sql fail, rollback.", err) + err = to.Rollback() + if err != nil { + logs.Error("roll back transaction failed", err) + } + } else { + err = to.Commit() + if err != nil { + logs.Error("commit transaction failed.", err) + } + } +``` + +Either way, it should be noted that only SQL executed via `TxOrm` will be considered to be within a transaction. + +```go +o := orm.NewOrm() +to, err := o.Begin() + +// outside the txn +o.Insert(xxx) + +// inside the txn +to.Insert(xxx) +``` + +Of course, `QuerySeter` and `QueryM2Mer`, `RawSeter` derived from `TxOrm` are also considered to be inside the transaction. + +The methods related to transactions are: + +```go + Begin() (TxOrmer, error) + BeginWithCtx(ctx context.Context) (TxOrmer, error) + BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) + BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) + + // Beego closure + DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error + DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error + DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error + DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error + +``` diff --git a/docs/en-US/v2.3.x/qa/README.md b/docs/en-US/v2.3.x/qa/README.md new file mode 100644 index 0000000..f4e1b1b --- /dev/null +++ b/docs/en-US/v2.3.x/qa/README.md @@ -0,0 +1,18 @@ +--- +title: Q & A +lang: en-US +--- + +# Q & A + +We use a lot of commands when answering these questions. In case you find that your platform does not have the command, please search for a replaceable command on your own. + +For Windows platforms, we strongly recommend using WSL. + +## FAQ - Frequently Asked Questions + +- [Why did the web server fail to start?](failed_to_start_web_server.md) +- [What to choose as the receiver for the Controller methods](./choose_func_recever_for_web.md) +- [What does CopyRequestBody mean?](./what-is-copy-request-body.md) +- [Why can't I read the contents of the HTTP body?](./why_can_not_read_data_from_body.md) +- [How to interrupt an HTTP request and return immediately?](../web/router/ctrl_style/controller.md) diff --git a/docs/en-US/v2.3.x/qa/choose_func_recever_for_web.md b/docs/en-US/v2.3.x/qa/choose_func_recever_for_web.md new file mode 100644 index 0000000..329682b --- /dev/null +++ b/docs/en-US/v2.3.x/qa/choose_func_recever_for_web.md @@ -0,0 +1,46 @@ +--- +title: What to choose as the receiver for the Controller methods +lang: en-US +--- + +# What to choose as the receiver for the Controller methods + +Inside our controller-style routing, we declare a `Controller` and all the methods for handling `HTTP` requests are defined on the `Controller`. + +Example: + +```go +import "github.com/beego/beego/v2/server/web" + +type UserController struct { + web.Controller +} + +func (u *UserController) HelloWorld() { + u.Ctx.WriteString("hello, world") +} +``` + +Note that the receiver we use here is the **Pointer receiver**. So can we not use a pointer receiver? + +The answer is yes: + +```go +import "github.com/beego/beego/v2/server/web" + +type UserController struct { + web.Controller +} + +func (u UserController) HelloWorld() { + u.Ctx.WriteString("hello, world") +} +``` + +It is no different from writing with a pointer receiver, and Beego handles both correctly. + +So the question is, which one should we use? +- Preferring the use of pointers, as this is in line with long-standing Beego practice +- If you use the `CtrlXXX` family methods for registration, consider using a non-pointer. Of course there is no functional difference, except that one is `(*UserController).HelloWord` and the other is `UserController.HelloWord`, the latter one looks more refreshing; + +For Beego, it is possible to use any receiver, **they do not differ in functionality**. The rest is a matter of elegance and personal preference. diff --git a/docs/en-US/v2.3.x/qa/failed_to_start_web_server.md b/docs/en-US/v2.3.x/qa/failed_to_start_web_server.md new file mode 100644 index 0000000..c1192dd --- /dev/null +++ b/docs/en-US/v2.3.x/qa/failed_to_start_web_server.md @@ -0,0 +1,44 @@ +--- +title: Why did the web server fail to start? +lang: en-US +--- + +## Why did the web server fail to start? + +The reasons for web server startup can be varied. Generally speaking, it fails because there is a problem with the local environment. web server startup failure generally consists of two parts. + +1. Application service failed to start +2. Admin service failed to start + +However, they fail for similar reasons because essentially, the admin service is one of our built-in services. + +### Port conflict + +This is the most common reason for service startup failure. By default, the application service port is 8080, while the admin service port is 8088. + +So we have to check the port situation first. You can run the command: + +```shell +lsof -i:8080 +``` + +`8080` can be replaced with your port, including the admin service port. + +If you find that process information is already output, e.g.: + +```shell +COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME +___go_bui 160824 xxxx 3u IPv6 82721234 0t0 TCP *:9090 (LISTEN) +``` + +Then it means that the port is occupied. In this case, you need to consider closing this process. + +First you need to make sure that the process is closeable. As we have observed, in most cases it is possible to close the process directly by typing in the command line. + +```shell +kill -9 160824 +``` + +`160824` is the PID, which is the process ID, please replace it by yourself. + +And then restart the services. diff --git a/docs/en-US/v2.3.x/qa/img/bee_version.png b/docs/en-US/v2.3.x/qa/img/bee_version.png new file mode 100644 index 0000000..ae22f58 Binary files /dev/null and b/docs/en-US/v2.3.x/qa/img/bee_version.png differ diff --git a/docs/en-US/v2.3.x/qa/img/qa/copy_request_body_false.png b/docs/en-US/v2.3.x/qa/img/qa/copy_request_body_false.png new file mode 100644 index 0000000..8c8f77e Binary files /dev/null and b/docs/en-US/v2.3.x/qa/img/qa/copy_request_body_false.png differ diff --git a/docs/en-US/v2.3.x/qa/img/qa/copy_request_body_true.png b/docs/en-US/v2.3.x/qa/img/qa/copy_request_body_true.png new file mode 100644 index 0000000..3fbf08b Binary files /dev/null and b/docs/en-US/v2.3.x/qa/img/qa/copy_request_body_true.png differ diff --git a/docs/en-US/v2.3.x/qa/what-is-copy-request-body.md b/docs/en-US/v2.3.x/qa/what-is-copy-request-body.md new file mode 100644 index 0000000..ac7ae69 --- /dev/null +++ b/docs/en-US/v2.3.x/qa/what-is-copy-request-body.md @@ -0,0 +1,77 @@ +--- +title: What does CopyRequestBody mean? +lang: en-US +--- + +# What does CopyRequestBody mean? + +In the Beego web configuration, there is a very confusing parameter called `CopyRequestBody`. It is in the structure `web.Config`. + +This parameter was introduced for purpose: Beego reads the HTTP request body data and performs some processing. Also, after Beego reads it, the user can read it again. + +There are two examples. + +The first example with `CopyRequestBody = true` + +```go + +func main() { + web.BConfig.CopyRequestBody = true + web.CtrlPost("/hello", (*MainController).ReadDataFromBody) + web.Run() +} + +type MainController struct { + web.Controller +} + +func (m *MainController) ReadDataFromBody() { + u := &user{} + err := m.Controller.BindJson(u) + if err != nil { + logs.Error("could not bind json data: %v", err) + } + err = m.JsonResp(u) + if err != nil { + logs.Error("could not write json resp: %v", err) + } +} +``` + +When we access `localhost:8080` and pass in the parameters, we can get the response: +![CopyRequestBody=true](./img/qa/copy_request_body_true.png) + +If we set `CopyRequestBody` to `false`: + +```go +func main() { + web.BConfig.CopyRequestBody = false + web.CtrlPost("/hello", (*MainController).ReadDataFromBody) + web.Run() +} + +type MainController struct { + web.Controller +} + +func (m *MainController) ReadDataFromBody() { + u := &user{} + err := m.Controller.BindJson(u) + if err != nil { + logs.Error("could not bind json data: %v", err) + } + err = m.JsonResp(u) + if err != nil { + logs.Error("could not write json resp: %v", err) + } +} +``` + +Then we will find that we cannot read the data from inside the request body: +![CopyRequestBody=false](./img/qa/copy_request_body_false.png) + +So, be aware that you should set `CopyRequestBody` to `true` if you intend to rely on Beego to process the request. + +`CopyRequestBody` should be considered a bit cumbersome by today's eyes. But the advantage of it is that you can read the data from Beego multiple times. After all, the `Body` field inside `http.Request` can only be read once. + +In that sense, it's worth keeping for now. But we are considering to set its default value to `true` instead of `false` in the future. diff --git a/docs/en-US/v2.3.x/qa/why_can_not_read_data_from_body.md b/docs/en-US/v2.3.x/qa/why_can_not_read_data_from_body.md new file mode 100644 index 0000000..b7718be --- /dev/null +++ b/docs/en-US/v2.3.x/qa/why_can_not_read_data_from_body.md @@ -0,0 +1,9 @@ +--- +title: Why can't I read the contents of the HTTP body? +lang: en-US +--- + +# Why can't I read the contents of the HTTP body? + +- `CopyRequestBody=false`, refer [`CopyRequestBody`参数](what-is-copy-request-body.md); +- There are already other Filter, Middleware read out body: By default, the `Request` inside `http` is designed as a stream structure, so the data inside can only be read once. Most of the time, if you want to read it repeatedly, you have to make a copy of this request body yourself; diff --git a/docs/en-US/v2.3.x/task/README.md b/docs/en-US/v2.3.x/task/README.md new file mode 100644 index 0000000..e43625c --- /dev/null +++ b/docs/en-US/v2.3.x/task/README.md @@ -0,0 +1,140 @@ +--- +title: Task +lang: en-US +--- + +# Task + +Tasks work very similarly to cron jobs. Tasks are used to run a job outside the normal request/response cycle. These can be adhoc or scheduled to run regularly. +Examples include: Reporting memory and goroutine status, periodically triggering GC or cleaning up log files at fixed intervals. + +### Creating a new Task + +To initialize a task implement : + + tk1 := task.NewTask("tk1", "0 12 * * * *", func(ctx context.Context) error { + fmt.Println("tk1") + return nil + }) + +The NewTask signature: + + NewTask(tname string, spec string, f TaskFunc) *Task + +- `tname`: Task name +- `spec`: Task format. See below for details. +- `f`: The function which will be run as the task. + +To implement this task, add it to the global task list and start it. + + task.AddTask("tk1", tk1) + task.StartTask() + defer task.StopTask() + +### Testing the TaskFunc + +Use the code below to test if the TaskFunc is working correctly. + + err := tk.Run() + if err != nil { + t.Fatal(err) + } + + +### spec in detail + +`spec` specifies when the new Task will be run. Its format is the same as that of traditional crontab: + +``` +// The first 6 parts are: +// second: 0-59 +// minute: 0-59 +// hour: 1-23 +// day: 1-31 +// month: 1-12 +// weekdays: 0-6(0 is Sunday) + +// Some special sign: +// *: any time +// ,: separator. E.g.: 2,4 in the third part means run at 2 and 4 o'clock +//   -: range. E.g.: 1-5 in the third part means run between 1 and 5 o'clock +// /n : run once every n time. E.g.: */1 in the third part means run once every an hour. Same as 1-23/1 +///////////////////////////////////////////////////////// +// 0/30 * * * * * run every 30 seconds +// 0 43 21 * * * run at 21:43 +// 0 15 05 * * * run at 05:15 +// 0 0 17 * * * run at 17:00 +// 0 0 17 * * 1 run at 17:00 of every Monday +// 0 0,10 17 * * 0,2,3 run at 17:00 and 17:10 of every Sunday, Tuesday and Wednesday +// 0 0-10 17 1 * * run once every minute from 17:00 to 7:10 on 1st day of every month +// 0 0 0 1,15 * 1 run at 0:00 on 1st and 15th of each month and every Monday +// 0 42 4 1 * * run at 4:42 on 1st of every month +// 0 0 21 * * 1-6 run at 21:00 from Monday to Saturday +// 0 0,10,20,30,40,50 * * * * run every 10 minutes +// 0 */10 * * * * run every 10 minutes +// 0 * 1 * * * run every one minute from 1:00 to 1:59 +// 0 0 1 * * * run at 1:00 +// 0 0 */1 * * * run at :00 of every hour +// 0 0 * * * * run at :00 of every hour +// 0 2 8-20/3 * * * run at 8:02, 11:02, 14:02, 17:02 and 20:02 +// 0 30 5 1,15 * * run at 5:30 of 1st and 15th of every month +``` + +## Debug module (Already moved to utils module) + +We always use print for debugging. But the default output is not good enough for debugging. Beego provides this debug module + +- Display() print result to console +- GetDisplayString() return the string + +It print key/value pairs. The following code: + + Display("v1", 1, "v2", 2, "v3", 3) + +will output: + + 2013/12/16 23:48:41 [Debug] at TestPrint() [/Users/astaxie/github/beego/task/debug_test.go:13] + + [Variables] + v1 = 1 + v2 = 2 + v3 = 3 + +For pointer type: + + type mytype struct { + next *mytype + prev *mytype + } + + var v1 = new(mytype) + var v2 = new(mytype) + + v1.prev = nil + v1.next = v2 + + v2.prev = v1 + v2.next = nil + + Display("v1", v1, "v2", v2) + +The output result + + 2013/12/16 23:48:41 [Debug] at TestPrintPoint() [/Users/astaxie/github/beego/task/debug_test.go:26] + + [Variables] + v1 = &task.mytype{ + next: &task.mytype{ + next: nil, + prev: 0x210335420, + }, + prev: nil, + } + v2 = &task.mytype{ + next: nil, + prev: &task.mytype{ + next: 0x210335430, + prev: nil, + }, + } + diff --git a/docs/en-US/v2.3.x/validation/README.md b/docs/en-US/v2.3.x/validation/README.md new file mode 100644 index 0000000..c86a2c4 --- /dev/null +++ b/docs/en-US/v2.3.x/validation/README.md @@ -0,0 +1,193 @@ +--- +title: Validation +lang: en-US +--- + +# Form validation + +The Form validation module is used for data validation and error collection. + +## Localization + +In order to localize validation error messages, one might use `SetDefaultMessage` function of the `validation` package. + +Note that format markers (`%d`, `%s`) must be preserved in translated text to provide resulting messages with validation context values. + +Default template messages are present in `validation.MessageTmpls` variable. + +Simple message localization for Russian language: + +```go +import "github.com/beego/beego/v2/core/validation" + +func init() { + validation.SetDefaultMessage(map[string]string{ + "Required": "Должно быть заполнено", + "Min": "Минимально допустимое значение %d", + "Max": "Максимально допустимое значение %d", + "Range": "Должно быть в диапазоне от %d до %d", + "MinSize": "Минимально допустимая длина %d", + "MaxSize": "Максимально допустимая длина %d", + "Length": "Длина должна быть равна %d", + "Alpha": "Должно состоять из букв", + "Numeric": "Должно состоять из цифр", + "AlphaNumeric": "Должно состоять из букв или цифр", + "Match": "Должно совпадать с %s", + "NoMatch": "Не должно совпадать с %s", + "AlphaDash": "Должно состоять из букв, цифр или символов (-_)", + "Email": "Должно быть в правильном формате email", + "IP": "Должен быть правильный IP адрес", + "Base64": "Должно быть представлено в правильном формате base64", + "Mobile": "Должно быть правильным номером мобильного телефона", + "Tel": "Должно быть правильным номером телефона", + "Phone": "Должно быть правильным номером телефона или мобильного телефона", + "ZipCode": "Должно быть правильным почтовым индексом", + }) +} +``` + +## Examples: + +Direct use: + +```go +import ( + "github.com/beego/beego/v2/core/validation" + "log" +) + +type User struct { + Name string + Age int +} + +func main() { + u := User{"man", 40} + valid := validation.Validation{} + valid.Required(u.Name, "name") + valid.MaxSize(u.Name, 15, "nameMax") + valid.Range(u.Age, 0, 18, "age") + + if valid.HasErrors() { + // If there are error messages it means the validation didn't pass + // Print error message + for _, err := range valid.Errors { + log.Println(err.Key, err.Message) + } + } + // or use like this + if v := valid.Max(u.Age, 140, "age"); !v.Ok { + log.Println(v.Error.Key, v.Error.Message) + } + // Customize error messages + minAge := 18 + valid.Min(u.Age, minAge, "age").Message("18+ only!!") + // Format error messages + valid.Min(u.Age, minAge, "age").Message("%d+", minAge) +} +``` +Use through StructTag + +```go +import ( + "log" + "strings" + + "github.com/beego/beego/v2/core/validation" +) + +// Set validation function in "valid" tag +// Use ";" as the separator of multiple functions. Spaces accept after ";" +// Wrap parameters with "()" and separate parameter with ",". Spaces accept after "," +// Wrap regex match with "//" +// +type user struct { + Id int + Name string `valid:"Required;Match(/^Bee.*/)"` // Name can't be empty or start with Bee + Age int `valid:"Range(1, 140)"` // 1 <= Age <= 140, only valid in this range + Email string `valid:"Email; MaxSize(100)"` // Need to be a valid Email address and no more than 100 characters. + Mobile string `valid:"Mobile"` // Must be a valid mobile number + IP string `valid:"IP"` // Must be a valid IPv4 address +} + +// If your struct implemented interface `validation.ValidFormer` +// When all tests in StructTag succeed, it will execute Valid function for custom validation +func (u *user) Valid(v *validation.Validation) { + if strings.Index(u.Name, "admin") != -1 { + // Set error messages of Name by SetError and HasErrors will return true + v.SetError("Name", "Can't contain 'admin' in Name") + } +} + +func main() { + valid := validation.Validation{} + u := user{Name: "Beego", Age: 2, Email: "dev@beego.vip"} + b, err := valid.Valid(&u) + if err != nil { + // handle error + } + if !b { + // validation does not pass + // blabla... + for _, err := range valid.Errors { + log.Println(err.Key, err.Message) + } + } +} +``` +Available validation functions in StrucTag: + +* `Required` not empty. :TODO 不为空,即各个类型要求不为其零值 +* `Min(min int)` minimum value. Valid type is `int`, all other types are invalid. +* `Max(max int)` maximum value. Valid type is `int`, all other types are invalid. +* `Range(min, max int)` Value range. Valid type is `int`, all other types are invalid. +* `MinSize(min int)` minimum length. Valid type is `string slice`, all other types are invalid. +* `MaxSize(max int)` maximum length. Valid type is `string slice`, all other types are invalid. +* `Length(length int)` fixed length. Valid type is `string slice`, all other types are invalid. +* `Alpha` alpha characters. Valid type is `string`, all other types are invalid. +* `Numeric` numerics. Valid type is `string`, all other types are invalid. +* `AlphaNumeric` alpha characters or numerics. Valid type is `string`, all other types are invalid. +* `Match(pattern string)` regex matching. Valid type is `string`, all other types will be cast to string then match. (fmt.Sprintf("%v", obj).Match) +* `AlphaDash` alpha characters or numerics or `-_`. Valid type is `string`, all other types are invalid. +* `Email` Email address. Valid type is `string`, all other types are invalid. +* `IP` IP address,Only support IPv4 address. Valid type is `string`, all other types are invalid. +* `Base64` base64 encoding. Valid type is `string`, all other types are invalid. +* `Mobile` mobile number. Valid type is `string`, all other types are invalid. +* `Tel` telephone number. Valid type is `string`, all other types are invalid. +* `Phone` mobile number or telephone number. Valid type is `string`, all other types are invalid. +* `ZipCode` zip code. Valid type is `string`, all other types are invalid. + +## Using label +Sometimes you don't want to use the field name in error message, so that you can use the `label` tag. + +Here is the simple example: + +```go +type User struct { + Age int `valid:"Required;Range(1, 140)" label:"age"` +} +``` + +And then the output error message is: "age Range is 1 to 140". + +## Custom validation +You can register your own validator via calling `AddCustomFunc`: +```go +type user struct { + // ... + Address string `valid:"ChinaAddress"` +} + +func main() { + _ = validation.AddCustomFunc("ChinaAddress", func(v *validation.Validation, obj interface{}, key string) { + addr, ok := obj.(string) + if !ok { + return + } + if !strings.HasPrefix(addr, "China") { + v.AddError(key, "China address only") + } + }) + // ... +} +``` \ No newline at end of file diff --git a/docs/en-US/v2.3.x/web/README.md b/docs/en-US/v2.3.x/web/README.md new file mode 100644 index 0000000..54a432a --- /dev/null +++ b/docs/en-US/v2.3.x/web/README.md @@ -0,0 +1,83 @@ +--- +title: Web 模块 +lang: en-US +sidebar: auto +--- + +# Web 模块 + +Here we are all enabled in programming mode, i.e. there will be no dependency on configuration files. Please refer to the module's [configuration description and configuration examples](./config.md) for almost all parameters that use configuration files. + +## Quickly Start + +Building a web server is as simple as writing the following in the code: + +```go +import ( + "github.com/beego/beego/v2/server/web" +) + +func main() { + // now you start the beego as http server. + // it will listen to port 8080 + web.Run() +} +``` + +In this case, the web server will use port `8080`, so please make sure that port is not occupied before starting. + +[Web examples](https://github.com/beego/beego-example/blob/master/httpserver/basic/main.go) + +## Basic Usage + +### Port + +If you wish to specify the port of the server, then you can pass in the port at startup time: + +```go +import ( + "github.com/beego/beego/v2/server/web" +) + +func main() { + // now you start the beego as http server. + // it will listen to port 8081 + web.Run(":8081") +} +``` + +### Host And Port + +We generally do not recommend this writing style as it does not seem necessary, i.e: + +```go +import ( + "github.com/beego/beego/v2/server/web" +) + +func main() { + // now you start the beego as http server. + // it will listen to port 8081 + web.Run("localhost:8081") + // or + web.Run("127.0.0.1:8081") +} +``` + +If you just specify the host, but not the port, then we will use the default port `8080`. For example: + +```go +import ( + "github.com/beego/beego/v2/server/web" +) + +func main() { + // now you start the beego as http server. + // it will listen to port 8080 + web.Run("localhost") +} +``` + +## Reference + +[Register routers](./router/README.md) diff --git a/docs/en-US/v2.3.x/web/admin/README.md b/docs/en-US/v2.3.x/web/admin/README.md new file mode 100644 index 0000000..654b9e6 --- /dev/null +++ b/docs/en-US/v2.3.x/web/admin/README.md @@ -0,0 +1,213 @@ +--- +title: Admin Service +lang: en-US +--- + +# Admin Service + +Start the admin service: + +```go +web.BConfig.Listen.EnableAdmin = true +``` + +And you can specify the admin service host and port: + +```go +web.BConfig.Listen.AdminAddr = "localhost" +web.BConfig.Listen.AdminPort = 8088 +``` + +Open web browser and types URL: http://localhost:8088/ you can see `Welcome to Admin Dashboard`. + +## 请求统计信息 + +Access `http://localhost:8088/qps`: + +![](./img/monitoring.png) + +How can I use the statistics? Add statistics like this: + + admin.StatisticsMap.AddStatistics("POST", "/api/user", "&admin.user", time.Duration(2000)) + admin.StatisticsMap.AddStatistics("POST", "/api/user", "&admin.user", time.Duration(120000)) + admin.StatisticsMap.AddStatistics("GET", "/api/user", "&admin.user", time.Duration(13000)) + admin.StatisticsMap.AddStatistics("POST", "/api/admin", "&admin.user", time.Duration(14000)) + admin.StatisticsMap.AddStatistics("POST", "/api/user/astaxie", "&admin.user", time.Duration(12000)) + admin.StatisticsMap.AddStatistics("POST", "/api/user/xiemengjun", "&admin.user", time.Duration(13000)) + admin.StatisticsMap.AddStatistics("DELETE", "/api/user", "&admin.user", time.Duration(1400)) + +Get statistics information: + + admin.StatisticsMap.GetMap(os.Stdout) + +Here is the output: + + | requestUrl | method | times | used | max used | min used | avg used | + | /api/user | POST | 2 | 122.00us | 120.00us | 2.00us | 61.00us | + | /api/user | GET | 1 | 13.00us | 13.00us | 13.00us | 13.00us | + | /api/user | DELETE | 1 | 1.40us | 1.40us | 1.40us | 1.40us | + | /api/admin | POST | 1 | 14.00us | 14.00us | 14.00us | 14.00us | + | /api/user/astaxie | POST | 1 | 12.00us | 12.00us | 12.00us | 12.00us | + | /api/user/xiemengjun | POST | 1 | 13.00us | 13.00us | 13.00us | 13.00us | + + +## Profiling + +Monitoring the performance of running processes is a very good way to optimize performance and to look for issues in our application. E.g.: information of GC and goroutine. + +Profile provides a easy entry point for you to debug the application. It uses the `ProcessInput` entry function to process the requests. Here are some debugging types: + +- lookup goroutine + + Print out the tasks of all goroutines which are currently running. You can easily see what all goroutines are doing. + + goroutine 3 [running]: + runtime/pprof.writeGoroutineStacks(0x634238, 0xc210000008, 0x62b000, 0xd200000000000000) + /Users/astaxie/go/src/pkg/runtime/pprof/pprof.go:511 +0x7c + runtime/pprof.writeGoroutine(0x634238, 0xc210000008, 0x2, 0xd2676410957b30fd, 0xae98) + /Users/astaxie/go/src/pkg/runtime/pprof/pprof.go:500 +0x3c + runtime/pprof.(*Profile).WriteTo(0x52ebe0, 0x634238, 0xc210000008, 0x2, 0x1, ...) + /Users/astaxie/go/src/pkg/runtime/pprof/pprof.go:229 +0xb4 + _/Users/astaxie/github/beego/toolbox.ProcessInput(0x2c89f0, 0x10, 0x634238, 0xc210000008) + /Users/astaxie/github/beego/toolbox/profile.go:26 +0x256 + _/Users/astaxie/github/beego/toolbox.TestProcessInput(0xc21004e090) + /Users/astaxie/github/beego/toolbox/profile_test.go:9 +0x5a + testing.tRunner(0xc21004e090, 0x532320) + /Users/astaxie/go/src/pkg/testing/testing.go:391 +0x8b + created by testing.RunTests + /Users/astaxie/go/src/pkg/testing/testing.go:471 +0x8b2 + + goroutine 1 [chan receive]: + testing.RunTests(0x315668, 0x532320, 0x4, 0x4, 0x1) + /Users/astaxie/go/src/pkg/testing/testing.go:472 +0x8d5 + testing.Main(0x315668, 0x532320, 0x4, 0x4, 0x537700, ...) + /Users/astaxie/go/src/pkg/testing/testing.go:403 +0x84 + main.main() + _/Users/astaxie/github/beego/toolbox/_test/_testmain.go:53 +0x9c + +- lookup heap + + Print out information of current heap: + + heap profile: 1: 288 [2: 296] @ heap/1048576 + 1: 288 [2: 296] @ + + + # runtime.MemStats + # Alloc = 275504 + # TotalAlloc = 275512 + # Sys = 4069608 + # Lookups = 5 + # Mallocs = 469 + # Frees = 1 + # HeapAlloc = 275504 + # HeapSys = 1048576 + # HeapIdle = 647168 + # HeapInuse = 401408 + # HeapReleased = 0 + # HeapObjects = 468 + # Stack = 24576 / 131072 + # MSpan = 4472 / 16384 + # MCache = 1504 / 16384 + # BuckHashSys = 1476472 + # NextGC = 342976 + # PauseNs = [370712 77378 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] + # NumGC = 2 + # EnableGC = true + # DebugGC = false + +- lookup threadcreate + + Print out information of threads: + + threadcreate profile: total 4 + 1 @ 0x17f68 0x183c7 0x186a8 0x188cc 0x19ca9 0xcf41 0x139a3 0x196c0 + # 0x183c7 newm+0x27 /Users/astaxie/go/src/pkg/runtime/proc.c:896 + # 0x186a8 startm+0xb8 /Users/astaxie/go/src/pkg/runtime/proc.c:974 + # 0x188cc handoffp+0x1ac /Users/astaxie/go/src/pkg/runtime/proc.c:992 + # 0x19ca9 runtime.entersyscallblock+0x129 /Users/astaxie/go/src/pkg/runtime/proc.c:1514 + # 0xcf41 runtime.notetsleepg+0x71 /Users/astaxie/go/src/pkg/runtime/lock_sema.c:253 + # 0x139a3 runtime.MHeap_Scavenger+0xa3 /Users/astaxie/go/src/pkg/runtime/mheap.c:463 + + 1 @ 0x17f68 0x183c7 0x186a8 0x188cc 0x189c3 0x1969b 0x2618b + # 0x183c7 newm+0x27 /Users/astaxie/go/src/pkg/runtime/proc.c:896 + # 0x186a8 startm+0xb8 /Users/astaxie/go/src/pkg/runtime/proc.c:974 + # 0x188cc handoffp+0x1ac /Users/astaxie/go/src/pkg/runtime/proc.c:992 + # 0x189c3 stoplockedm+0x83 /Users/astaxie/go/src/pkg/runtime/proc.c:1049 + # 0x1969b runtime.gosched0+0x8b /Users/astaxie/go/src/pkg/runtime/proc.c:1382 + # 0x2618b runtime.mcall+0x4b /Users/astaxie/go/src/pkg/runtime/asm_amd64.s:178 + + 1 @ 0x17f68 0x183c7 0x170bc 0x196c0 + # 0x183c7 newm+0x27 /Users/astaxie/go/src/pkg/runtime/proc.c:896 + # 0x170bc runtime.main+0x3c /Users/astaxie/go/src/pkg/runtime/proc.c:191 + + 1 @ + +- lookup block + + Print out information of block: + + --- contention: + cycles/second=2294781025 + +- start cpuprof + + Start recording cpuprof info into created file cpu-pid.pprof. + +- stop cpuprof + + Stop recording. + +- get memprof + + Start recording memprof into created file mem-pid.memprof + +- gc summary + + Check GC status: + + NumGC:2 Pause:54.54us Pause(Avg):170.82us Overhead:177.49% Alloc:248.97K Sys:3.88M Alloc(Rate):1.23G/s Histogram:287.09us 287.09us 287.09us + +## Health Check + + +It can check the health status of your application. E.g.: To check if database is available: + +```go +type DatabaseCheck struct { +} + +func (dc *DatabaseCheck) Check() error { + if dc.isConnected() { + return nil + } else { + return errors.New("can't connect database") + } +} +``` +Then you can add it as a check item: + +```go +admin.AddHealthCheck("database",&DatabaseCheck{}) +``` + +After this you can send get request to `/healthcheck`: + + $ curl http://beego.vip:8088/healthcheck + * deadlocks: OK + * database: OK + +It will return the database status accordingly. + +## 定时任务 + +The user needs to have added [Timed Tasks](../../task/README.md) to the application in order to perform the corresponding task checks and manually triggered tasks. + +- `http://localhost:8088/task` +- or: `http://localhost:8088/task?taskname=task name` + +## Configuration information + +- All configuration `http://localhost:8088/listconf?command=conf` +- Routers: `http://localhost:8088/listconf?command=router` +- Filters: `http://localhost:8088/listconf?command=filter` diff --git a/docs/en-US/v2.3.x/web/admin/img/monitoring.png b/docs/en-US/v2.3.x/web/admin/img/monitoring.png new file mode 100644 index 0000000..bc74bf4 Binary files /dev/null and b/docs/en-US/v2.3.x/web/admin/img/monitoring.png differ diff --git a/docs/en-US/v2.3.x/web/config.md b/docs/en-US/v2.3.x/web/config.md new file mode 100644 index 0000000..2f6461c --- /dev/null +++ b/docs/en-US/v2.3.x/web/config.md @@ -0,0 +1,403 @@ +--- +title: Web 模块配置 +lang: en-US +--- + +# Web Module Configuration + +Here we mainly discuss the individual configuration items of the Web module, and how to use the configuration module, you can refer to[Configure Module](../config/README.md) + +## Default Configuration Files + +`BConfig` is the default web configuration instance and Beego will parse the `conf/app.conf` file by default. +Example: + +```ini + appname = beepkg + httpaddr = "127.0.0.1" + httpport = 9090 + runmode ="dev" + autorender = false + recoverpanic = false + viewspath = "myview" +``` + +Note here that the configuration items are not named using camel naming, but are kept in all lowercase. However, different configuration formats can support both camel naming and underscore naming. For example, the JSON configuration format is only related to the value of the json tag inside your structure. + +## Specify Configuration Files + +```go +const ConfigFile = "./my-custom.conf" +func main() { + err := web.LoadAppConfig("ini", ConfigFile) + if err != nil { + logs.Critical("An error occurred:", err) + panic(err) + } + + val, _ := web.AppConfig.String("name") + + logs.Info("load config name is", val) +} +``` + +You can call `LoadAppConfig` multiple times, if the later file conflicts with the previous key, then the latest loaded one will be the latest value. + +## Code As Configuration + +When the application is simple, or when the configuration items have the same value in different environments, you can consider using code for configuration. It has the advantage of being able to fully enjoy compile-time type checking, but of course the disadvantage is also very obvious, i.e., it is not flexible enough and cannot be dynamically modified at runtime. + +The configuration of the Web module in Beego is defined in the structure: + +```go +type Config struct { + // ... +} +``` + +Example: + +```go +web.BConfig.AppName="My-App" +``` + +## Configuration Items + +The detailed field meanings can be directly referred to the source code [Config definition](https://github.com/beego/beego/blob/develop/server/web/config.go) + +### App config + +* AppName + + The application name. By default this is "Beego". If the application is created by `bee new project_name` it will be set to project_name. + + `beego.BConfig.AppName = "beego"` + +* RunMode + + The application mode. By default this is set to `dev`. Other valid modes include `prod` and `test`. In `dev` mode user friendly error pages will be shown. In `prod` mode user friendly error pages will not be rendered. + + `beego.BConfig.RunMode = "dev"` + +* RouterCaseSensitive + + Set case sensitivity for the router. By default this value is true. + + `beego.BConfig.RouterCaseSensitive = true` + +* ServerName + + The Beego server name. By default this name is `beego`. + + `beego.BConfig.ServerName = "beego"` + +* RecoverPanic + + When active the application will recover from exceptions without exiting the application. By default this is set to true. + + `beego.BConfig.RecoverPanic = true` + +* CopyRequestBody + + Toggle copying of raw request body in context. By default this is false except for GET, HEAD or file uploading. + + `beego.BConfig.CopyRequestBody = false` + +* EnableGzip + + Enable Gzip. By default this is false. If Gzip is enabled the output of templates will be compressed by Gzip or zlib according to the `Accept-Encoding` setting of the browser. + + `beego.BConfig.EnableGzip = false` + + Further properties can be configured as below: + + `gzipCompressLevel = 9` Sets the compression level used for deflate compression(0-9). By default is 9 (best speed). + + `gzipMinLength = 256` Original content will only be compressed if length is either unknown or greater than gzipMinLength. The default length is 20B. + + `includedMethods = get;post` List of HTTP methods to compress. By default only GET requests are compressed. + +* MaxMemory + + Sets the memory cache size for file uploading. By default this is `1 << 26`(64M). + + `beego.BConfig.MaxMemory = 1 << 26` + +* EnableErrorsShow + + Toggles the display of error messages. By default this is True. + + `beego.BConfig.EnableErrorsShow = true` + +* EnableErrorsRender + + Toggles rendering error messages. By default this is set to True. User friendly error pages will not be rendered even in dev `RunMode` if this value is false. + +### Web config + +* AutoRender + + Enable auto render. By default this is True. This value should be set to false for API applications, as there is no need to render templates. + + `beego.BConfig.WebConfig.AutoRender = true` + +* EnableDocs + + Enable Docs. By default this is False. + + `beego.BConfig.WebConfig.EnableDocs = false` + +* FlashName + + Sets the Flash Cookie name. By default this is `BEEGO_FLASH`. + + `beego.BConfig.WebConfig.FlashName = "BEEGO_FLASH"` + +* FlashSeperator + + Set the Flash data separator. By default this is `BEEGOFLASH`. + + `beego.BConfig.WebConfig.FlashSeperator = "BEEGOFLASH"` + +* DirectoryIndex + + Enable listing of the static directory. By default this is False and will return a 403 error. + + `beego.BConfig.WebConfig.DirectoryIndex = false` + +* StaticDir + + Sets the static file dir(s). By default this is `static`. + + 1. Single dir, `StaticDir = download`. Same as `beego.SetStaticPath("/download","download")` + + 2. Multiple dirs, `StaticDir = download:down download2:down2`. Same as `beego.SetStaticPath("/download","down")` and `beego.SetStaticPath("/download2","down2")` + + `beego.BConfig.WebConfig.StaticDir = map[string]string{"download":"download"}` + + +* StaticExtensionsToGzip + + Sets a list of file extensions that will support compression by Gzip. The formats `.css` and `.js` are supported by default. + + `beego.BConfig.WebConfig.StaticExtensionsToGzip = []string{".css", ".js"}` + + Same as in config file StaticExtensionsToGzip = .css, .js + +* TemplateLeft + + Left mark of the template, `{{` by default. + + `beego.BConfig.WebConfig.TemplateLeft = "{{"` + +* TemplateRight + + Right mark of the template, `}}` by default. + + `beego.BConfig.WebConfig.TemplateRight = "}}"` + +* ViewsPath + + Set the location of template files. This is set to `views` by default. + + `beego.BConfig.WebConfig.ViewsPath = "views"` + +* EnableXSRF + Enable XSRF + + `beego.BConfig.WebConfig.EnableXSRF = false` + +* XSRFKEY + + Set the XSRF key. By default this is `beegoxsrf`. + + `beego.BConfig.WebConfig.XSRFKEY = "beegoxsrf"` + +* XSRFExpire + + Set the XSRF expire time. By default this is set to `0`. + + `beego.BConfig.WebConfig.XSRFExpire = 0` + +* CommentRouterPath + + Beego scan `CommentRouterPath` to auto generate router, the default value is `controllers`。 + `beego.BConfig.WebConfig.CommentRouterPath = "controllers"` + +### HTTP Server config + +* Graceful + + Enable graceful shutdown. By default this is False. + + `beego.BConfig.Listen.Graceful = false` + +* ServerTimeOut + + Set the http timeout. By default thi is '0', no timeout. + + `beego.BConfig.Listen.ServerTimeOut = 0` + +* ListenTCP4 + +Set the address type. default is `tcp6` but we can set it to true to force use `TCP4`. + + `beego.BConfig.Listen.ListenTCP4 = true` + +* EnableHTTP + + Enable HTTP listen. By default this is set to True. + + `beego.BConfig.Listen.EnableHTTP = true` + +* HTTPAddr + + Set the address the app listens to. By default this value is empty and the app will listen to all IPs. + + `beego.BConfig.Listen.HTTPAddr = ""` + +* HTTPPort + + Set the port the app listens on. By default this is 8080 + + `beego.BConfig.Listen.HTTPPort = 8080` + +* EnableHTTPS + + Enable HTTPS. By default this is False. When enabled `HTTPSCertFile` and `HTTPSKeyFile` must also be set. + + `beego.BConfig.Listen.EnableHTTPS = false` + +* HTTPSAddr + + Set the address the app listens to. Default is empty and the app will listen to all IPs. + + `beego.BConfig.Listen.HTTPSAddr = ""` + +* HTTPSPort + + Set the port the app listens on. By default this is 10443 + + `beego.BConfig.Listen.HTTPSPort = 10443` + +* HTTPSCertFile + + Set the SSL cert path. By default this value is empty. + + `beego.BConfig.Listen.HTTPSCertFile = "conf/ssl.crt"` + +* HTTPSKeyFile + + Set the SSL key path. By default this value is empty. + + `beego.BConfig.Listen.HTTPSKeyFile = "conf/ssl.key"` + +* EnableAdmin + + Enable supervisor module. By default this is False. + + `beego.BConfig.Listen.EnableAdmin = false` + +* AdminAddr + + Set the address the admin app listens to. By default this is blank and the app will listen to any IP. + + `beego.BConfig.Listen.AdminAddr = ""` + +* AdminPort + + Set the port the admin app listens on. By default this is 8088. + + `beego.BConfig.Listen.AdminPort = 8088` + +* EnableFcgi + + Enable fastcgi. By default this is False. + + `beego.BConfig.Listen.EnableFcgi = false` + +* EnableStdIo + + Enable fastcgi standard I/O or not. By default this is False. + + `beego.BConfig.Listen.EnableStdIo = false` + +### Session config + +* SessionOn + + Enable session. By default this is False. + + `beego.BConfig.WebConfig.Session.SessionOn = false` + +* SessionProvider + + Set the session provider. By default this is `memory`. + + `beego.BConfig.WebConfig.Session.SessionProvider = "memory"` + +* SessionName + + Set the session cookie name stored in the browser. By default this is `beegosessionID`. + + `beego.BConfig.WebConfig.Session.SessionName = "beegosessionID"` + +* SessionGCMaxLifetime + + Set the session expire time. By default this is 3600s. + + `beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600` + +* SessionProviderConfig + + Set the session provider config. Different providers can require different config settings. Please see [session](en-US/module/session.md) for more information. + +* SessionCookieLifeTime + + Set the valid expiry time of the cookie in browser for session. By default this is 3600s. + + `beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600` + +* SessionAutoSetCookie + + Enable SetCookie. By default this is True. + + `beego.BConfig.WebConfig.Session.SessionAutoSetCookie = true` + +* SessionDomain + + Set the session cookie domain. By default this is empty. + + `beego.BConfig.WebConfig.Session.SessionDomain = ""` + +### Log config + + See [logs module](en-US/module/logs.md) for more information. + +* AccessLogs + + Enable output access logs. By default these logs will not be output under 'prod' mode. + + `beego.BConfig.Log.AccessLogs = false` + +* FileLineNum + + Toggle printing line numbers. By default this is True. This config is not supported in config file. + + `beego.BConfig.Log.FileLineNum = true` + +* Outputs + + Log outputs config. This config is not supported in config file. + + `beego.BConfig.Log.Outputs = map[string]string{"console": ""}` + + or + + `beego.BConfig.Log.Outputs["console"] = ""` + + + +## 相关内容 + +- [配置模块](../config/README.md) diff --git a/docs/en-US/v2.3.x/web/context/README.md b/docs/en-US/v2.3.x/web/context/README.md new file mode 100644 index 0000000..2c325a7 --- /dev/null +++ b/docs/en-US/v2.3.x/web/context/README.md @@ -0,0 +1,12 @@ +--- +title: Web Context +lang: en-US +--- + +# Web Context + +Beego's `Context` object can basically be thought of as representing the entire context in which the web request is processed. Therefore, the `Context` part is all related to input and output, which can be found in: + +- [Input](../input/README.md) +- [Output](../output/README.md) +- [File](../file/README.md) diff --git a/docs/en-US/v2.3.x/web/cookie/README.md b/docs/en-US/v2.3.x/web/cookie/README.md new file mode 100644 index 0000000..a6ced29 --- /dev/null +++ b/docs/en-US/v2.3.x/web/cookie/README.md @@ -0,0 +1,81 @@ +--- +title: Cookie +lang: en-US +--- + +# Cookie + +[Cookie example](https://github.com/beego/beego-example/tree/master/httpserver/cookie) + +## Basic Usage + +- `GetCookie(key string)` +- `SetCookie(name string, value string, others ...interface{})` + +Example: + +```go +type MainController struct { + web.Controller +} + +func (ctrl *MainController) PutCookie() { + // put something into cookie,set Expires time + ctrl.Ctx.SetCookie("name", "web cookie", 10) + + // web-example/views/hello_world.html + ctrl.TplName = "hello_world.html" + ctrl.Data["name"] = "PutCookie" + _ = ctrl.Render() +} + +func (ctrl *MainController) ReadCookie() { + // web-example/views/hello_world.html + ctrl.TplName = "hello_world.html" + ctrl.Data["name"] = ctrl.Ctx.GetCookie("name") + // don't forget this + _ = ctrl.Render() +} +``` + +`others` means: + +- others[0]: `maxAge`, means `Expires` and `Max-Age` +- others[1]: `Path`, string, the default value is `/` +- others[2]: `Domain`, string +- others[3]: `Secure`, bool +- others[4]: `HttpOnly`, bool +- others[5]: `SameSite`, string + +## Encryption + +Beego provides two methods to assist with cookie encryption, it uses `sha256` as the encryption algorithm and `Secret` as the encryption key: + +- `GetSecureCookie(Secret, key string) (string, bool)` +- `SetSecureCookie(Secret, name, value string, others ...interface{})` + +```go +type MainController struct { + web.Controller +} + +func (ctrl *MainController) PutSecureCookie() { + // put something into cookie,set Expires time + ctrl.Ctx.SetSecureCookie("my-secret", "name", "web cookie") + + // web-example/views/hello_world.html + ctrl.TplName = "hello_world.html" + ctrl.Data["name"] = "PutCookie" + _ = ctrl.Render() +} + +func (ctrl *MainController) ReadSecureCookie() { + // web-example/views/hello_world.html + ctrl.TplName = "hello_world.html" + ctrl.Data["name"], _ = ctrl.Ctx.GetSecureCookie("my-secret", "name") + // don't forget this + _ = ctrl.Render() +} +``` + +Please refer to above section to learn `others`. \ No newline at end of file diff --git a/docs/en-US/v2.3.x/web/error/README.md b/docs/en-US/v2.3.x/web/error/README.md new file mode 100644 index 0000000..9afc757 --- /dev/null +++ b/docs/en-US/v2.3.x/web/error/README.md @@ -0,0 +1,149 @@ +--- +title: Error Handling +lang: en-US +--- + +# Error Handling + +When we do web development, we often need page jumping and error handling, and Beego has taken this into account, using the `Redirect` method for redirecting: + +```go +func (this *AddController) Get() { + this.Redirect("/", 302) +} +``` + +To abort the request and throw an exception, Beego can do this in the controller: + +```go +func (this *MainController) Get() { + this.Abort("401") + v := this.GetSession("asta") + if v == nil { + this.SetSession("asta", int(1)) + this.Data["Email"] = 0 + } else { + this.SetSession("asta", v.(int)+1) + this.Data["Email"] = v.(int) + } + this.TplName = "index.tpl" +} +``` + +So that the code after `this.Abort("401")` will not be executed and the following page will be displayed to the user by default: + +![](./img/401.png) + +The web framework supports 401, 403, 404, 500, 503 error handling by default. Users can customize the corresponding error handling, for example, the following redefines the 404 page: + +```go +func page_not_found(rw http.ResponseWriter, r *http.Request){ + t,_:= template.New("404.html").ParseFiles(web.BConfig.WebConfig.ViewsPath+"/404.html") + data :=make(map[string]interface{}) + data["content"] = "page not found" + t.Execute(rw, data) +} + +func main() { + web.ErrorHandler("404",page_not_found) + web.Router("/", &controllers.MainController{}) + web.Run() +} +``` + +We can handle 404 errors by customizing the error page `404.html`. + +Another more user-friendly aspect of Beego is its support for user-defined string error type handling functions, such as the following code, where the user registers a database error handling page: + +```go +func dbError(rw http.ResponseWriter, r *http.Request){ + t,_:= template.New("dberror.html").ParseFiles(web.BConfig.WebConfig.ViewsPath+"/dberror.html") + data :=make(map[string]interface{}) + data["content"] = "database is now down" + t.Execute(rw, data) +} + +func main() { + web.ErrorHandler("dbError",dbError) + web.Router("/", &controllers.MainController{}) + web.Run() +} +``` + +Once you register this error handling code in the entry, then you can call `this.Abort("dbError")` for exception page handling whenever you encounter a database error in your logic. + +## Controller define Error +Beego version 1.4.3 added support for Controller defined Error handlers, so we can use the `web.Controller` and `template.Render` context functions + +```go + +package controllers + +import ( + "github.com/beego/beego/v2/server/web" +) + +type ErrorController struct { + web.Controller +} + +func (c *ErrorController) Error404() { + c.Data["content"] = "page not found" + c.TplName = "404.tpl" +} + +func (c *ErrorController) Error500() { + c.Data["content"] = "internal server error" + c.TplName = "500.tpl" +} + +func (c *ErrorController) ErrorDb() { + c.Data["content"] = "database is now down" + c.TplName = "dberror.tpl" +} +``` +From the example we can see that all the error handling functions have the prefix `Error`,the other string is the name of `Abort`,like `Error404` match `Abort("404")` + +Use `web.ErrorController` to register the error controller before `web.Run` + +```go +package main + +import ( + _ "btest/routers" + "btest/controllers" + + "github.com/beego/beego/v2/server/web" +) + +func main() { + web.ErrorController(&controllers.ErrorController{}) + web.Run() +} + +``` + + +## Panic Handling + +If you want the user to be able to return a response even if a panic occurs while the server is processing the request, then you can use Beego's recovery mechanism. This mechanism is enabled by default. + +```go +web.BConfig.RecoverPanic = true +``` + +If you want to customize the processing behavior after `panic`, then you can reset `web.BConfig.RecoverFunc`: + +```go + web.BConfig.RecoverFunc = func(context *context.Context, config *web.Config) { + if err := recover(); err != nil { + context.WriteString(fmt.Sprintf("you panic, err: %v", err)) + } + } +``` + +Always be careful: you always need to detect the result of `recover` and put the logic for recovering from `panic` inside the code that detects that `recover` does not return `nil`. + +## Reference + +-[Controller API - Interrupt](../router/ctrl_style/controller.md) diff --git a/docs/en-US/v2.3.x/web/error/img/401.png b/docs/en-US/v2.3.x/web/error/img/401.png new file mode 100644 index 0000000..a603936 Binary files /dev/null and b/docs/en-US/v2.3.x/web/error/img/401.png differ diff --git a/docs/en-US/v2.3.x/web/file/README.md b/docs/en-US/v2.3.x/web/file/README.md new file mode 100644 index 0000000..5578c52 --- /dev/null +++ b/docs/en-US/v2.3.x/web/file/README.md @@ -0,0 +1,78 @@ +--- +title: File Download Upload +lang: en-US +--- + +# File Download Upload + +## Upload + +You can easily handle file uploads in Beego, just don't forget to add this attribute `enctype="multipart/form-data"` to your form, otherwise your browser won't transfer your uploaded file. + +If the file size is larger than the set cache memory size, then it will be placed in a temporary file, the default cache memory is 64M, you can adjust this cache memory size by following: + +``` +web.MaxMemory = 1<<22 +``` + +Or in configuration files: + +``` +maxmemory = 1<<22 +``` + +At the same time, Beego provides another parameter, `MaxUploadSize`, to limit the maximum upload file size - if you upload multiple files at once, then it limits the size of all those files combined together. + +By default, `MaxMemory` should be set smaller than `MaxUploadSize`, and the effect of combining the two parameters in this case is: + +1. if the file size is smaller than `MaxMemory`, it will be processed directly in memory. +2. if the file size is between `MaxMemory` and `MaxUploadSize`, the portion larger than `MaxMemory` will be placed in a temporary directory. +3. the file size exceeds `MaxUploadSize`, the request is rejected directly and the response code is returned 413 + +Beego provides two very convenient ways to handle file uploads: + +- `GetFile(key string) (multipart.File, *multipart.FileHeader, error)` + +- `SaveToFile(fromfile, tofile string) error` + +```html +
+ + +
+``` +Saving file example: + +```go +func (c *FormController) Post() { + f, h, err := c.GetFile("uploadname") + if err != nil { + log.Fatal("getfile err ", err) + } + defer f.Close() + c.SaveToFile("uploadname", "static/upload/" + h.Filename) +} +``` + +## Download + +```go +func (output *BeegoOutput) Download(file string, filename ...string) {} +``` + +Example: + +```go +func (ctrl *MainController) DownloadFile() { + // The file LICENSE is under root path. + // and the downloaded file name is license.txt + ctrl.Ctx.Output.Download("LICENSE", "license.txt") +} +``` +In particular, note that the first parameter of the `Download` method is the file path, that is, the file to be downloaded; the second parameter is an indefinite parameter, representing the file name when the user saves it locally. + +If the first parameter uses a relative path, then it represents a relative path calculated from the current working directory. + +## Reference + +- [Static files](./../view/static_file.md) diff --git a/docs/en-US/v2.3.x/web/filter/README.md b/docs/en-US/v2.3.x/web/filter/README.md new file mode 100644 index 0000000..9fc14f9 --- /dev/null +++ b/docs/en-US/v2.3.x/web/filter/README.md @@ -0,0 +1,460 @@ +--- +title: Web Filter +lang: en-US +--- + +# Web Filter + +`filter` is the solution for AOP that we provide at Beego. It is applied not only in `web`, but also in the rest of the modules. + +In Beego, `filter` has two responsibilities, one is as an implementation of `AOP` and the other is as a hook in the request lifecycle. So to understand `filter` you have to understand Beego's request handling process first. + +## Quick Start + +```go +import ( + "fmt" + + "github.com/beego/beego/v2/server/web" + "github.com/beego/beego/v2/server/web/context" +) + +func main() { + + ctrl := &MainController{} + web.InsertFilter("/user/*", web.BeforeExec, filterFunc) + web.Run() +} + +func filterFunc(ctx *context.Context) { + fmt.Println("do something here") +} +``` + +Here we can see that the so-called `filter`, is a method whose parameter is `*context.Context`, and this is its definition: + +```go +// FilterFunc defines a filter function which is invoked before the controller handler is executed. +// It's a alias of HandleFunc +// In fact, the HandleFunc is the last Filter. This is the truth +type FilterFunc = HandleFunc + +// HandleFunc define how to process the request +type HandleFunc func(ctx *beecontext.Context) +``` + +> Note that we consider `filter` to be just a special kind of `handler`, so here `FilterFunc` is an alias for `HandleFunc`. From this perspective, we think that the last place to process a request is the last `filter`. + +And InsertFilter: + +```go +// InserFilter see HttpServer.InsertFilter +func InsertFilter(pattern string, pos int, filter FilterFunc, opts ...FilterOpt) *HttpServer { + // ... +} +``` + +- `pattern`: string or regex to match against router rules. Use `/*` to match all. +- `pos`: the place to execute the Filter. There are five fixed parameters representing different execution processes. + - web.BeforeStatic: Before finding the static file. + - web.BeforeRouter: Before finding router. + - web.BeforeExec: After finding router and before executing the matched Controller. + - web.AfterExec: After executing Controller. + - web.FinishRouter: After finishing router. +- `filter`: filter function type FilterFunc func(*context.Context) +- `opts`: + - `web.WithReturnOnOutput`: sets the value of `returnOnOutput` (default `true`), if there is already output before this filter is performed, whether to not continue to execute this filter, the default setting is that if there is already output before (parameter `true`), then this filter will not be executed. + - `web.WithResetParams`: Whether to reset the `filter` parameter, the default is `false`, because in case of conflict between the `pattern` of the `filter` and the `pattern` of the route itself, you can reset the `filter` parameter, so as to ensure that the correct parameter is obtained in the subsequent logic, for example, if the filter of `/api/*` is set, and the router of `/api/docs/*` is set at the same time, then when the router of `/api/docs/*` is executed, then the correct parameter is obtained in the subsequent logic. For example, if you set the filter of `/api/*` and also set the router of `/api/docs/*`, then when you access `/api/docs/swagger/abc.js`, set `:splat` parameter to `docs/swagger/abc.js` when executing `filter`, but if the option is `false`, it will keep `docs/swagger/abc.js` when executing the routing logic, and reset the `:splat` parameter if `true` is set. + - `web.WithCaseSensitive`: case sensitive; + +If it is not clear how to use these options, the best way is to write a few tests yourself to experiment with their effects. + +Here is an example to authenticate if the user is logged in for all requests: + +```go +var FilterUser = func(ctx *context.Context) { + if strings.HasPrefix(ctx.Input.URL(), "/login") { + return + } + + _, ok := ctx.Input.Session("uid").(int) + if !ok { + ctx.Redirect(302, "/login") + } +} + +web.InsertFilter("/*", web.BeforeRouter, FilterUser) +``` + +Be aware that to access the `Session` method, the `pos` parameter cannot be set to `BeforeStatic`. + +More details for `pattern` refer [router](./../router/router_rule.md) + +## Update Existing Router + +Custom router during runtime: + +```go +var UrlManager = func(ctx *context.Context) { + // 数据库读取全部的 url mapping 数据 + urlMapping := model.GetUrlMapping() + for baseurl,rule:=range urlMapping { + if baseurl == ctx.Request.RequestURI { + ctx.Input.RunController = rule.controller + ctx.Input.RunMethod = rule.method + break + } + } +} + +web.InsertFilter("/*", web.BeforeRouter, web.UrlManager) +``` + +## Filter And FilterChain + +In v1.x, we can't invoke next `Filter` inside a `Filter`. So we got a problem: we could not do something "surrounding" request execution. + +For example, if we want to do: +``` +func filter() { + // do something before serving request + handleRequest() + // do something after serving request +} +``` + +The typical cases are tracing and metrics. + +So we enhance `Filter` by designing a new interface: + +```go +type FilterChain func(next FilterFunc) FilterFunc +``` + +Here is a simple example: + +```go +package main + +import ( + "github.com/beego/beego/v2/core/logs" + "github.com/beego/beego/v2/server/web" + "github.com/beego/beego/v2/server/web/context" +) + +func main() { + web.InsertFilterChain("/*", func(next web.FilterFunc) web.FilterFunc { + return func(ctx *context.Context) { + // do something + logs.Info("hello") + // don't forget this + next(ctx) + + // do something + } + }) +} +``` + +In this example, we only output "hello" and then we invoke next filter. + +### Prometheus例子 + +```go +package main + +import ( + "time" + + "github.com/beego/beego/v2/server/web" + "github.com/beego/beego/v2/server/web/filter/prometheus" +) + +func main() { + // we start admin service + // Prometheus will fetch metrics data from admin service's port + web.BConfig.Listen.EnableAdmin = true + + web.BConfig.AppName = "my app" + + ctrl := &MainController{} + web.Router("/hello", ctrl, "get:Hello") + fb := &prometheus.FilterChainBuilder{} + web.InsertFilterChain("/*", fb.FilterChain) + web.Run(":8080") + // after you start the server + // and GET http://localhost:8080/hello + // access http://localhost:8088/metrics + // you can see something looks like: + // http_request_web_sum{appname="my app",duration="1002",env="prod",method="GET",pattern="/hello",server="webServer:1.12.1",status="200"} 1002 + // http_request_web_count{appname="my app",duration="1002",env="prod",method="GET",pattern="/hello",server="webServer:1.12.1",status="200"} 1 + // http_request_web_sum{appname="my app",duration="1004",env="prod",method="GET",pattern="/hello",server="webServer:1.12.1",status="200"} 1004 + // http_request_web_count{appname="my app",duration="1004",env="prod",method="GET",pattern="/hello",server="webServer:1.12.1",status="200"} 1 +} + +type MainController struct { + web.Controller +} + +func (ctrl *MainController) Hello() { + time.Sleep(time.Second) + ctrl.Ctx.ResponseWriter.Write([]byte("Hello, world")) +} +``` + +If you don't use Beego's admin service, don't forget to expose `prometheus`'s port. + +### Opentracing例子 + +```go +package main + +import ( + "time" + + "github.com/beego/beego/v2/server/web" + "github.com/beego/beego/v2/server/web/filter/opentracing" +) + +func main() { + // don't forget this to inject the opentracing API's implementation + // opentracing2.SetGlobalTracer() + + web.BConfig.AppName = "my app" + + ctrl := &MainController{} + web.Router("/hello", ctrl, "get:Hello") + fb := &opentracing.FilterChainBuilder{} + web.InsertFilterChain("/*", fb.FilterChain) + web.Run(":8080") + // after you start the server +} + +type MainController struct { + web.Controller +} + +func (ctrl *MainController) Hello() { + time.Sleep(time.Second) + ctrl.Ctx.ResponseWriter.Write([]byte("Hello, world")) +} + +``` + +Don't forget to using `SetGlobalTracer` to initialize opentracing. + + +## Builtin Filters + +We provide a series of `filter`s that you can enable or disable, depending on the situation. + +### Prometheus + +```go +package main + +import ( + "time" + + "github.com/beego/beego/v2/server/web" + "github.com/beego/beego/v2/server/web/filter/prometheus" +) + +func main() { + // we start admin service + // Prometheus will fetch metrics data from admin service's port + web.BConfig.Listen.EnableAdmin = true + + web.BConfig.AppName = "my app" + + ctrl := &MainController{} + web.Router("/hello", ctrl, "get:Hello") + fb := &prometheus.FilterChainBuilder{} + web.InsertFilterChain("/*", fb.FilterChain) + web.Run(":8080") + // after you start the server + // and GET http://localhost:8080/hello + // access http://localhost:8088/metrics + // you can see something looks like: + // http_request_web_sum{appname="my app",duration="1002",env="prod",method="GET",pattern="/hello",server="webServer:1.12.1",status="200"} 1002 + // http_request_web_count{appname="my app",duration="1002",env="prod",method="GET",pattern="/hello",server="webServer:1.12.1",status="200"} 1 + // http_request_web_sum{appname="my app",duration="1004",env="prod",method="GET",pattern="/hello",server="webServer:1.12.1",status="200"} 1004 + // http_request_web_count{appname="my app",duration="1004",env="prod",method="GET",pattern="/hello",server="webServer:1.12.1",status="200"} 1 +} + +type MainController struct { + web.Controller +} + +func (ctrl *MainController) Hello() { + time.Sleep(time.Second) + ctrl.Ctx.ResponseWriter.Write([]byte("Hello, world")) +} +``` + +### Opentracing + +```go +package main + +import ( + "time" + + "github.com/beego/beego/v2/server/web" + "github.com/beego/beego/v2/server/web/filter/opentracing" +) + +func main() { + // don't forget this to inject the opentracing API's implementation + // opentracing2.SetGlobalTracer() + + web.BConfig.AppName = "my app" + + ctrl := &MainController{} + web.Router("/hello", ctrl, "get:Hello") + fb := &opentracing.FilterChainBuilder{} + web.InsertFilterChain("/*", fb.FilterChain) + web.Run(":8080") + // after you start the server +} + +type MainController struct { + web.Controller +} + +func (ctrl *MainController) Hello() { + time.Sleep(time.Second) + ctrl.Ctx.ResponseWriter.Write([]byte("Hello, world")) +} + +``` + +### Api Auth Filter + +There are two points to understand when using the api auth filter: + +- How to access this filter +- How to generate the correct signature + +Basic usage: + +```go + +// import "github.com/beego/beego/v2/server/web/filter/apiauth" +web.InsertFilter("/", web.BeforeRouter, apiauth.APIBasicAuth("myid", "mykey")) +``` + +Where `mykey` is the key used to verify the signature, and is also the key needed when the upstream call is initiated. This access scheme is very simple, `beego` internal implementation will read `appid` from the request, and then if `appid` happens to be `myid`, `mykey` will be used to generate the signature and compare it with the signature also read from the parameters. If they are equal, the request will be processed, otherwise the request will be rejected and a `403` error will be returned. + +Another usage is a custom method of finding keys based on `appid`: + +```go + // import "github.com/beego/beego/v2/server/web/filter/apiauth" + web.InsertFilter("/", web.BeforeRouter, apiauth.APISecretAuth(func(appid string) string { + // Here's how you define how to find the key based on the app id + return appid + "key" + }, 300)) +``` + +Note that `300` represents the timeout parameter. + +- The filter relies on reading `appid` from the request parameters and finding the key based on `appid` +- The filter relies on reading `timestamp`, the timestamp, from the request parameter, which is in the format `2006-01-02 15:04:05` +- The filter relies on reading the signature `signature` from the request parameters, and `beego` compares the read signature with its own signature generated from the key, i.e. forensics + +In addition, as the caller, you can use the `apiauth.Signature` method directly to generate a signature to put inside the request to call the downstream. + +Note that we do not recommend using this forensic filter on the public `API`. This is because the implementation has only basic functionality and does not have strong security - it is extremely key dependent. If its own key is exposed, then an attacker can easily generate the correct key based on the encryption method used by `beego`. The specific higher-security forensic implementation is out of the scope of `beego` and can be understood by developers who need it. + +### Auth Filter + +This filter is very similar to the previous api auth filter. But the two have different mechanisms. `apiauth` uses a signature mechanism and focuses on applications calling each other. This one should be called an authentication filter, which focuses on identification, and its internal mechanism is to use a username and password, similar to the login process. + +This filter, reads the `token` from the request header `Authorization`. Currently, `beego` only supports the `Basic` encryption method, looks like: +``` +Authorization Basic your-token +``` + +`beego` internally reads this `token` and decodes it to get the username and password it carries. `beego` compares the username and password to see if they match, a process that requires the developer to tell `beego` how to match the username and password when initializing the filter. + +The basic usage: + +```go +// import "github.com/beego/beego/v2/server/web/filter/auth" +web.InsertFilter("/", web.BeforeRouter, auth.Basic("your username", "your pwd")) +``` + +Or: + +```go + // import "github.com/beego/beego/v2/server/web/filter/auth" + web.InsertFilter("/", web.BeforeRouter, auth.NewBasicAuthenticator(func(username, pwd string) bool { + // validation here + }, "your-realm")) + web.Run() +``` + +where `your-realm` is simply placed in the response header as an error message if the validation fails。 + +### Authz Filter + +This filter is also authentication, not authentication. It focuses on whether **the user has access to a resource** compared to the previous two filters. Like the `Auth Filter`, it parses the username from the `Authorization` header, except that this filter doesn't care about passwords. + +Or rather, it should be called `Casbin` filter. For details, you can read [Casbin github](https://github.com/casbin/casbin). Note that `beego` is still using its `v1` version, while as of now they have been upgraded to `v2`. + +After that, the filter combines the `http method` and the request path to determine if the user has permission. If there is permission, then `beego` will process the request. + +Example: + +```go +// import "github.com/beego/beego/v2/server/web/filter/authz" +web.InsertFilter("/", web.BeforeRouter, authz.NewAuthorizer(casbin.NewEnforcer("path/to/basic_model.conf", "path/to/basic_policy.csv"))) + +``` + +More details refer to [Casbin github](https://github.com/casbin/casbin) + +### CORS Filter + + +```go + // import "github.com/beego/beego/v2/server/web/filter/cors" + web.InsertFilter("/", web.BeforeRouter, cors.Allow(&cors.Options{ + AllowAllOrigins: true, + })) +``` + +With this setting, requests coming under whatever domain are allowed. If you want to do fine-grained control, you can adjust the parameter value of `Options`。 + +### Rate Limit Filter + +```go + +// import "github.com/beego/beego/v2/server/web/filter/ratelimit" +web.InsertFilter("/", web.BeforeRouter, ratelimit.NewLimiter()) + +``` + +The token bucket algorithm is mainly influenced by two parameters, one is the capacity and the other is the rate. By default, the capacity is set to `100` and the rate is set to generate one token every ten milliseconds. + +- `WithCapacity` +- `WithRate` +- `WithRejectionResponse`: the response if rejecting the request +- `WithSessionKey`: For example if the flow is restricted relative to a particular `API`, then the route for that `API` can be returned. In this case, then `web.BeforeRouter` should not be used, but `web.BeforeExec` should be used instead. + +### Session Filter + +This is experimental in nature, we try to support controlling `session` in different dimensions. So this `filter` is introduced. + +```go + // "github.com/beego/beego/v2/server/web" + // "github.com/beego/beego/v2/server/web/filter/session" + // websession "github.com/beego/beego/v2/server/web/session" + web.InsertFilterChain("/need/session/path", session.Session(websession.ProviderMemory)) +``` + +More details refer [session](../session/README.md) + +## Reference + +- [Router](./../router/router_rule.md) diff --git a/docs/en-US/v2.3.x/web/grace.md b/docs/en-US/v2.3.x/web/grace.md new file mode 100644 index 0000000..03ea09c --- /dev/null +++ b/docs/en-US/v2.3.x/web/grace.md @@ -0,0 +1,50 @@ +--- +title: Web Hot Upgrade +lang: en-US +--- + +# 热升级 + +What is a hot upgrade? If you know about nginx, you know that it supports hot upgrades, where you can use old processes to serve previously linked links and new processes to serve new links, i.e., you can upgrade the system and modify the operating parameters without stopping the service. + +The main idea of Beego comes from: http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/ + +```go + import( + "log" + "net/http" + "os" + "strconv" + + "github.com/beego/beego/v2/server/web/grace" + ) + + func handler(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("WORLD!")) + w.Write([]byte("ospid:" + strconv.Itoa(os.Getpid()))) + } + + func main() { + mux := http.NewServeMux() + mux.HandleFunc("/hello", handler) + + err := grace.ListenAndServe("localhost:8080", mux) + if err != nil { + log.Println(err) + } + log.Println("Server on 8080 stopped") + os.Exit(0) + } +``` + +Open two terminals: + +One input: `ps -ef|grep 应用名` + +One input: `curl "http://127.0.0.1:8080/hello"` + +And then + +`kill -HUP ${procces ID}` + +Open other terminal, input: `curl "http://127.0.0.1:8080/hello?sleep=0"` diff --git a/docs/en-US/v2.3.x/web/input/README.md b/docs/en-US/v2.3.x/web/input/README.md new file mode 100644 index 0000000..3f73470 --- /dev/null +++ b/docs/en-US/v2.3.x/web/input/README.md @@ -0,0 +1,165 @@ +--- +title: Input +lang: en-US +--- + +# Input + +In general, processing input relies heavily on the methods provided by the `Controller`. And specific inputs can be found in: + +- Path variable: refer to [router](../router/router_rule.md) +- Query parameter +- Body: To read data from the request body, int most cases setting `BConfig.CopyRequestBody` to `true` is enough. If you create more than one `web.Server`, then you must set `CopyRequestBody` to `true` in each `Server` instance + +And the methods of obtaining parameters can be divided into two main categories: + +- The first category is methods prefixed with Get: this is a large category of methods that try to get the value of a particular parameter +- The second category is methods prefixed with Bind: this is a large category of methods that attempt to convert input into a structure + +## Get + +For this type of method, Beego reads from two main places: the query parameters and the form, and if there are parameters with the same name in both places, then Beego returns the data inside the form. For example: + +```go +type MainController struct { + web.Controller +} + +func (ctrl *MainController) Post() { + name := ctrl.GetString("name") + if name == "" { + ctrl.Ctx.WriteString("Hello World") + return + } + ctrl.Ctx.WriteString("Hello " + name) +} +``` + +When we access. + +- Path `localhost:8080?name=a`: it will output `Hello, a` +- path `localhost:8080`, and then the form is submitted with `name=b`, then the output will be `b` +- path `localhost:8080?name=a`, and the form is submitted with `name=b`, then `b` will be output + +Methods in this category also allow default values to be passed in: + +```go +func (ctrl *MainController) Get() { + name := ctrl.GetString("name", "Tom") + ctrl.Ctx.WriteString("Hello " + name) +} +``` + +If we don't pass the `name` parameter, then `Tom` will be used as the value of `name`, for example when we access `GET localhost:8080`, it will output `Hello Tom`. + +It should be noted that the method signature of `GetString` is: + +```go +func (c *Controller) GetString(key string, def ...string) string { + // ... +} +``` + +Note that although `def` is declared as a variable parameter, in practice, Beego will only use the first default value, and all subsequent ones will be ignored. + +The method signatures and behaviors are similar for this class, they are: + +- `GetString(key string, def ...string) string` +- `GetStrings(key string, def ...[]string) []string` +- `GetInt(key string, def ...int) (int, error)` +- `GetInt8(key string, def ...int8) (int8, error)` +- `GetUint8(key string, def ...uint8) (uint8, error)` +- `GetInt16(key string, def ...int16) (int16, error)` +- `GetUint16(key string, def ...uint16) (uint16, error)` +- `GetInt32(key string, def ...int32) (int32, error)` +- `GetUint32(key string, def ...uint32) (uint32, error)` +- `GetInt64(key string, def ...int64) (int64, error)` +- `GetUint64(key string, def ...uint64) (uint64, error)` +- `GetBool(key string, def ...bool) (bool, error)` +- `GetFloat(key string, def ...float64) (float64, error)` + +Note that `GetString` and `GetStrings` themselves are not designed to return `error`, so you can't get an error. + +## Bind + +Most of the time, we also need to convert the input to a structure, and Beego provides a series of methods to do the input-to-structure binding. + +This part of the method is defined directly on the `Context` structure, so the user can manipulate the `Context` instance directly. To simplify the operation, we have defined similar methods on the `Controller`. + +```go + +// 要设置 web.BConfig.CopyRequestBody = true + +type MainController struct { + web.Controller +} + +func (ctrl *MainController) Post() { + user := User{} + err := ctrl.BindJSON(&user) + if err != nil { + ctrl.Ctx.WriteString(err.Error()) + return + } + ctrl.Ctx.WriteString(fmt.Sprintf("%v", user)) +} + +type User struct { + Age int `json:"age"` + Name string `json:"name"` +} +``` + + +- `Bind(obj interface{}) error`: Based on `Content-Type` +- `BindYAML(obj interface{}) error` +- `BindForm(obj interface{}) error` +- `BindJSON(obj interface{}) error` +- `BindProtobuf(obj proto.Message) error` +- `BindXML(obj interface{}) error` + + +Note that although we provide a way to determine how to bind based on `Content-Type`, we prefer users to use the bind method that specifies the format. + +> An API that should only accept input in a particular format, such as JSON only, and should not be able to handle multiple inputs + +In the early days, Beego also had a method similar to `BindForm`: `ParseForm(obj interface{}) error`, both of which have the same effect. + +## Path Variable + +Refer [Path Variable Router ](../router/router_rule.md) + +## Historical Bind methods + +In Beego's `Input` there is a family of methods defined for reading parameters. This class of methods is very similar to the `Get` family of methods. + +Example: + +```url +?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie +``` + +```go +var id int +this.Ctx.Input.Bind(&id, "id") //id ==123 + +var isok bool +this.Ctx.Input.Bind(&isok, "isok") //isok ==true + +var ft float64 +this.Ctx.Input.Bind(&ft, "ft") //ft ==1.2 + +ol := make([]int, 0, 2) +this.Ctx.Input.Bind(&ol, "ol") //ol ==[1 2] + +ul := make([]string, 0, 2) +this.Ctx.Input.Bind(&ul, "ul") //ul ==[str array] + +user struct{Name} +this.Ctx.Input.Bind(&user, "user") //user =={Name:"astaxie"} +``` + +## Reference + +- [Path Variable Router](../router/router_rule.md) +- [File Handling](../file/README.md) diff --git a/docs/en-US/v2.3.x/web/output/README.md b/docs/en-US/v2.3.x/web/output/README.md new file mode 100644 index 0000000..8a818c6 --- /dev/null +++ b/docs/en-US/v2.3.x/web/output/README.md @@ -0,0 +1,107 @@ +--- +title: Ouput +lang: en-US +--- + +# Output + +Beego provides a variety of APIs to help users export to the client, supporting several data formats such as JSON, XML or YAML. + +These methods are mainly defined on `Controller` and `Context`. + +For example, `WriteString`: + +```go +ctrl.Ctx.WriteString("Hello") +``` + +## Controller Serve + +`Controller` has a number of `Serve` methods that can be used to output JSON, XML or YAML data. + +The `Serve` class method is used in two steps: first, setting up the `Data`, and second, refreshing to the front-end. + +- JSON: + + ```go + func (this *AddController) Get() { + mystruct := { ... } + this.Data["json"] = &mystruct + this.ServeJSON() + } + ``` + + When ServeJSON is called, `content-type` is set to `application/json`, and the data is output JSON serialized at the same time. + +- XML: + + ```go + func (this *AddController) Get() { + mystruct := { ... } + this.Data["xml"]=&mystruct + this.ServeXML() + } + ``` + + After calling ServeXML, the `content-type` is set to `application/xml`, and the data is serialized for XML output. + +- jsonp + + ```go + func (this *AddController) Get() { + mystruct := { ... } + this.Data["jsonp"] = &mystruct + this.ServeJSONP() + } + ``` + + After calling ServeJSONP, the `content-type` is set to `application/javascript`, and then the data is JSON serialized at the same time, and then the jsonp output is set according to the callback parameter of the request. + +### Based On Accept Header + +In general, we do recommend that the backend directly specify the format of the response data. For example, using JSON format directly as data input and output inside an application. + +But there are times when we have to be compatible with multiple data formats, so consider using the `SetData` method and the `ServeFormatted` method. For example: + +```go +func (this *AddController) Get() { + mystruct := { ... } + this.SetData(&mystruct) + this.ServeFormatted() +} +``` + +Both are based on the `Accept` header field in the HTTP request to infer what format of data should be output as a response. If there is no `Accept` header, or if the field cannot be parsed, JSON formatted data will be output by default. + +## Context Methods + +If a functional routing style is used, that means we don't have a `Controller` to use. This is the time to consider using the methods on `Context`. + +Example: + +```go +func (this *AddController) Get() { + mystruct := { ... } + this.Ctx.JSONResp(&mystruct) +} +``` + +These methods all take an `interface` as input and try to serialize the input. That is, if the input is a string, then the `WriteString` method should be considered. + +- `JSONResp(data interface{}) error` +- `YamlResp(data interface{}) error` +- `ProtoResp(data proto.Message)` +- `XMLResp(data interface{}) error` + +### Based On Accept Header + +If you want to output response data based on the `Accept` field inside the HTTP request, then you should use the `Resp` method: + +```go +func (this *AddController) Get() { + mystruct := { ... } + this.Ctx.Resp(&mystruct) +} +``` + +If there is no `Accept` header, or if it cannot be parsed, then the data will be output using JSON. diff --git a/docs/en-US/v2.3.x/web/router/README.md b/docs/en-US/v2.3.x/web/router/README.md new file mode 100644 index 0000000..ebd685d --- /dev/null +++ b/docs/en-US/v2.3.x/web/router/README.md @@ -0,0 +1,9 @@ +--- +title: Register Routes +lang: en-US +--- + +# Register Routes + +1. [controller style](./ctrl_style/README.md) +2. [functional style](./functional_style/README.md) diff --git a/docs/en-US/v2.3.x/web/router/best_practice.md b/docs/en-US/v2.3.x/web/router/best_practice.md new file mode 100644 index 0000000..5e1667d --- /dev/null +++ b/docs/en-US/v2.3.x/web/router/best_practice.md @@ -0,0 +1,8 @@ +--- +title: Best Practices +lang: en-US +--- + +# Preference for functional style route registration + +The core reason is that this registration style is the most convenient and close to the features of the Go language itself. This registration style is basically supported by all major web frameworks today. diff --git a/docs/en-US/v2.3.x/web/router/ctrl_style/README.md b/docs/en-US/v2.3.x/web/router/ctrl_style/README.md new file mode 100644 index 0000000..a107ce9 --- /dev/null +++ b/docs/en-US/v2.3.x/web/router/ctrl_style/README.md @@ -0,0 +1,329 @@ +--- +title: Controller Style Router +lang: en-US +--- + +# Controller Style Router + +[Controller API](controller.md) + +## Basic Usage + +```go + +import "github.com/beego/beego/v2/server/web" + +type UserController struct { + web.Controller +} +``` + +Adding methods: + +```go +import "github.com/beego/beego/v2/server/web" + +type UserController struct { + web.Controller +} + +func (u *UserController) HelloWorld() { + u.Ctx.WriteString("hello, world") +} +func main() { + web.AutoRouter(&UserController{}) + web.Run() +} +``` + +Accessing URL `http://127.0.0.1:8080/user/helloworld`: + +![hello world.png](img/hello_world.png) + +Note that the methods inside the controller that handle `http` requests must be public methods - i.e. **initially capitalized and have no parameters and no return value**. If your method does not meet this requirement, in most cases, a `panic` will occur, e.g. if your method has parameters: + +```go +func (u *UserController) HelloWorldNoPtr(name string) { + u.Ctx.WriteString("don't work") +} +``` + +> Note that the `HandleFunc` in functional style actually takes a `*Context` parameter + +Or: + +```go +func (u UserController) HelloWorldNoPtr() { + u.Ctx.WriteString("don't work") +} +``` + +The general convention is to use a pointer receiver, but this is not mandatory. For a discussion of receivers, see[choose receiver](../../../qa/choose_func_recever_for_web.md) + +### Controller Name + +In some of the smarter APIs, we use the `Controller` name as a prefix, namespace, etc. + +In general: + +```go +type CtrlNameController struct { + +} +``` + +For example, if we define a `UserController`, then the name of the `Controller` is `User`. If it is case-insensitive, then `user` is also a legal name. + +Then let's say we define a `BuyerRefundController`, then `BuyerRefund` is the name, and when case insensitive, `buyerrefund` is also the legal name. + +## AutoRouter + +The routing rules resolved by `AutoRouter` are determined by the value of `RouterCaseSensitive`, the name of `Controller` and the method name. + +Where `UserController` it's name is `User` and the method name is `HelloWorld`. Then. + +- If `RouterCaseSensitive` is `true`, then `AutoRouter` registers two routes, `/user/helloworld/*`, `/User/HelloWorld/*`. +- If `RouterCaseSensitive` is `false`, then one route will be registered, `/user/helloworld/*`. + +**In summary, it is always correct to use all lowercase paths when using `AutoRouter`**. + +## AutoPrefix + +`AutoRouter` is internally based on the implementation of `AutoPrefix`, so to speak, the name of the `Controller`, which is the registered prefix (prefix). + +Example: + +```go +import ( + "github.com/beego/beego/v2/server/web" +) + +type UserController struct { + web.Controller +} + +func (u *UserController) HelloWorld() { + u.Ctx.WriteString("Hello, world") +} + +func main() { + // get http://localhost:8080/api/user/helloworld + // you will see return "Hello, world" + ctrl := &UserController{} + web.AutoPrefix("api", ctrl) + web.Run() +} +``` + +Accessing`http://localhost:8080/api/user/helloworld` and then see "Hello, world"。 + +Similar to `AutoRoute`: + +- If `RouterCaseSensitive` is `true`, then `AutoPrefix` registers two routes, `api/user/helloworld/*`, `api/User/HelloWorld/*`. + +- If `RouterCaseSensitive` is `false`, then one route will be registered, `api/user/helloworld/*`. + +Here we can summarize the rules of a general nature: **When we use `AutoPrefix`, the registered route matches the pattern `prefix/ctrlName/methodName`. ** + +## Manual + +If we don't want to use `AutoRoute` or `AutoPrefix` to register routes, because both of them depend on the name of the `Controller` and also on the name of the method. We may expect more flexibility. + +In this scenario, we might consider say, using manual registration to register routes one by one. + +In v2.0.2 we introduced a completely new way of registration: + +```go +import ( + "github.com/beego/beego/v2/server/web" +) + +type UserController struct { + web.Controller +} + +func (u *UserController) GetUserById() { + u.Ctx.WriteString("GetUserById") +} + +func (u *UserController) UpdateUser() { + u.Ctx.WriteString("UpdateUser") +} + +func (u *UserController) UserHome() { + u.Ctx.WriteString("UserHome") +} + +func (u *UserController) DeleteUser() { + u.Ctx.WriteString("DeleteUser") +} + +func (u *UserController) HeadUser() { + u.Ctx.WriteString("HeadUser") +} + +func (u *UserController) OptionUsers() { + u.Ctx.WriteString("OptionUsers") +} + +func (u *UserController) PatchUsers() { + u.Ctx.WriteString("PatchUsers") +} + +func (u *UserController) PutUsers() { + u.Ctx.WriteString("PutUsers") +} + +func main() { + + // get http://localhost:8080/api/user/123 + web.CtrlGet("api/user/:id", (*UserController).GetUserById) + + // post http://localhost:8080/api/user/update + web.CtrlPost("api/user/update", (*UserController).UpdateUser) + + // http://localhost:8080/api/user/home + web.CtrlAny("api/user/home", (*UserController).UserHome) + + // delete http://localhost:8080/api/user/delete + web.CtrlDelete("api/user/delete", (*UserController).DeleteUser) + + // head http://localhost:8080/api/user/head + web.CtrlHead("api/user/head", (*UserController).HeadUser) + + // patch http://localhost:8080/api/user/options + web.CtrlOptions("api/user/options", (*UserController).OptionUsers) + + // patch http://localhost:8080/api/user/patch + web.CtrlPatch("api/user/patch", (*UserController).PatchUsers) + + // put http://localhost:8080/api/user/put + web.CtrlPut("api/user/put", (*UserController).PutUsers) + + web.Run() +} +``` + +It is important to note that our new registration method, requires that when we pass in the method, we pass in `(*YourController).MethodName`. This is because of a Go language feature that requires that if you wish to get the method when the receiver is a pointer, then you should use `(*YourController)`. + +Without pointer: + +```go +import ( + "github.com/beego/beego/v2/server/web" +) + +type UserController struct { + web.Controller +} + +func (u UserController) GetUserById() { + u.Ctx.WriteString("GetUserById") +} + +func (u UserController) UpdateUser() { + u.Ctx.WriteString("UpdateUser") +} + +func (u UserController) UserHome() { + u.Ctx.WriteString("UserHome") +} + +func (u UserController) DeleteUser() { + u.Ctx.WriteString("DeleteUser") +} + +func (u UserController) HeadUser() { + u.Ctx.WriteString("HeadUser") +} + +func (u UserController) OptionUsers() { + u.Ctx.WriteString("OptionUsers") +} + +func (u UserController) PatchUsers() { + u.Ctx.WriteString("PatchUsers") +} + +func (u UserController) PutUsers() { + u.Ctx.WriteString("PutUsers") +} + +func main() { + + // get http://localhost:8080/api/user/123 + web.CtrlGet("api/user/:id", UserController.GetUserById) + + // post http://localhost:8080/api/user/update + web.CtrlPost("api/user/update", UserController.UpdateUser) + + // http://localhost:8080/api/user/home + web.CtrlAny("api/user/home", UserController.UserHome) + + // delete http://localhost:8080/api/user/delete + web.CtrlDelete("api/user/delete", UserController.DeleteUser) + + // head http://localhost:8080/api/user/head + web.CtrlHead("api/user/head", UserController.HeadUser) + + // patch http://localhost:8080/api/user/options + web.CtrlOptions("api/user/options", UserController.OptionUsers) + + // patch http://localhost:8080/api/user/patch + web.CtrlPatch("api/user/patch", UserController.PatchUsers) + + // put http://localhost:8080/api/user/put + web.CtrlPut("api/user/put", UserController.PutUsers) + + web.Run() +} +``` + +We recommend that if you use this family of methods, then you should choose to use a structured receiver so that the code looks much cleaner. + +The extra thing to notice is the `CtrlAny` method, which means that any `http` method can be handled. + +### Historical Methods + +Historically, we have registered routes in such a way: + +```go +func main() { + + ctrl := &MainController{} + + // we register the path / to &MainController + // if we don't pass methodName as third param + // web will use the default mappingMethods + // GET http://localhost:8080 -> Get() + // POST http://localhost:8080 -> Post() + // ... + web.Router("/", ctrl) + + // GET http://localhost:8080/health => ctrl.Health() + web.Router("/health", ctrl, "get:Health") + + // POST http://localhost:8080/update => ctrl.Update() + web.Router("/update", ctrl, "post:Update") + + // support multiple http methods. + // POST or GET http://localhost:8080/update => ctrl.GetOrPost() + web.Router("/getOrPost", ctrl, "get,post:GetOrPost") + + // support any http method + // POST, GET, PUT, DELETE... http://localhost:8080/update => ctrl.Any() + web.Router("/any", ctrl, "*:Any") + + web.Run() +} +``` + +**We no longer recommend using this approach because readability and maintainability are not good. Especially when refactoring for method renaming, it is easy to make mistakes.** + +## Reference + +[check routes](../router_tree.md) + +[choose receiver](../../../qa/choose_func_recever_for_web.md) +[Controller API](controller.md) +[route rules](../router_rule.md) diff --git a/docs/en-US/v2.3.x/web/router/ctrl_style/controller.md b/docs/en-US/v2.3.x/web/router/ctrl_style/controller.md new file mode 100644 index 0000000..c87ee4f --- /dev/null +++ b/docs/en-US/v2.3.x/web/router/ctrl_style/controller.md @@ -0,0 +1,42 @@ +--- +title: Controller API +lang: en-US +--- + +# Controller API + +The Controller, the main concept of Beego, takes on many responsibilities and can be divided into: + +- [Input](../../input/README.md) +- [Output](../../output/README.md) +- Callbacks or Hooks +- [Template Render](../../view/README.md) +- Interruption +- [Session](../../session/README.md) +- [Cookie](../../cookie/README.md) +- [XSRF](./../../xsrf/README.md) + +## Callbacks or Hooks + +When discussing the lifecycle, two concepts should be clarified: + +- The lifecycle of the entire Beego application +- The lifecycle of a single request being processed + +Here we discuss the second one: the life cycle of a single request being processed. + +The Controller customizes several hook functions. + +- `Prepare()`: It will be called before each request is executed +- `Finish()`: It will be called after each request is executed + +## Interruption + +Two methods are provided inside the `Controller`. + +- `(c *Controller) Abort(code string)`: This method will return the user response immediately. If the corresponding `code` developer has registered the error handling logic, then the error handling logic will be executed. If not, only an error code will be output. For how to register the handling logic, please refer to [Error Handling](../../error/README.md) +- `(c *Controller) CustomAbort(status int, body string)`: This method will output both `status` and `body` to the user. Where `status` is the HTTP response code. But if the corresponding HTTP response code already has error handling logic registered, then the `Abort` method should be used because `CustomAbort` will `panic` if the response code already has error handling logic registered. + +There is also a method `StopRun` in the `Controller`. This method is more dangerous than the previous two methods because it doesn't output an error response directly to the user, but happens `panic` directly. Then if the user's `RecoverPanic` option is `true`, then the logic to recover from `recover` will be executed. + +`RecoverPanic` please refer to the description in `web.Config`. diff --git a/docs/en-US/v2.3.x/web/router/ctrl_style/img/hello_world.png b/docs/en-US/v2.3.x/web/router/ctrl_style/img/hello_world.png new file mode 100644 index 0000000..f83a35a Binary files /dev/null and b/docs/en-US/v2.3.x/web/router/ctrl_style/img/hello_world.png differ diff --git a/docs/en-US/v2.3.x/web/router/functional_style/README.md b/docs/en-US/v2.3.x/web/router/functional_style/README.md new file mode 100644 index 0000000..33782cc --- /dev/null +++ b/docs/en-US/v2.3.x/web/router/functional_style/README.md @@ -0,0 +1,85 @@ +--- +title: Functional Style +lang: en-US +--- + +# Functional Style + +This style is closer to the syntax of Go itself, so we tend to recommend using this route registration method. + +Example: + +```go +func main() { + web.Get("/hello", func(ctx *context.Context) { + ctx.WriteString("hello, world") + }) + + web.Run() +} +``` + +Note that inside the functional style of writing, we only need to provide a method that uses the `*context.Context` argument. This `context` is not GO's `context` package, but Beego's `context` package. + +Or: + +```go +func main() { + + web.Post("/post/hello", PostHello) + + web.Run() +} + +func PostHello(ctx *context.Context) { + ctx.WriteString("hello") +} +``` + +All APIs: + +``` +Get(rootpath string, f HandleFunc) +Post(rootpath string, f HandleFunc) +Delete(rootpath string, f HandleFunc) +Put(rootpath string, f HandleFunc) +Head(rootpath string, f HandleFunc) +Options(rootpath string, f HandleFunc) +Patch(rootpath string, f HandleFunc) +Any(rootpath string, f HandleFunc) +``` +## Suggestions + +One of the difficulties to face when using a functional registration approach is how to organize these methods. + +In the controller style, all methods are naturally split by the controller. For example, in a simple system, we can have a `UserController`, and then all the methods related to `User` can be defined in this `Controller`; all the methods related to orders can be defined in the `OrderController`. + +So from this point of view, `Controller` provides a natural way to organize these methods. + +Well, in the functional style, we lack such a design. + +An intuitive way is to organize them by files. For example, all user-related ones in their own project `api/user.go`. + +If there are more methods, then one should further consider organizing by packages, which is also recommended. For example, creating a `user` directory under `api`, where all the methods for handling user-related requests are defined. + +The last way, is to define a `Controller` by itself, but this `Controller` only has the effect of organizing the code, and is not the kind we emphasize inside the controller style. + +For example, we can write a `Controller` without using `web.Controller`: + +```go +type UserController struct { + +} + +func (ctrl UserController) AddUser(ctx *context.Context) { + // you business code +} +``` + +**This `Controller` cannot be used for controller style reason registration**. It's just to provide a similar way to organize the code. + +## Reference + +[route rules](../router_rule.md) + +[namespace](../namespace.md) diff --git a/docs/en-US/v2.3.x/web/router/namespace.md b/docs/en-US/v2.3.x/web/router/namespace.md new file mode 100644 index 0000000..94404dc --- /dev/null +++ b/docs/en-US/v2.3.x/web/router/namespace.md @@ -0,0 +1,188 @@ +--- +title: Namespace +lang: en-US +--- + +# Namespace + +`namespace` is a logical means of organizing the API provided by Beego. Most of the time, when we register routes, we organize them according to certain rules, so using `namespace` will make the code more concise and maintainable. + +For example, our whole application is divided into two blocks, one for the API provided by Android and one for the API provided by IOS. then overall, it can be divided into two namespaces; some applications will have the concept of version, for example, V1 in the early days, V2 was introduced later, and then V3 was introduced later, then the whole application will have three namespaces. Different versions of APIs are registered under different namespaces. + +`namespace` is slightly more complicated, so you may need to write a few more simple `demos` to master its usage. + +## Examples + +```go +func main() { + uc := &UserController{} + // create namespace + ns := web.NewNamespace("/v1", + web.NSCtrlGet("/home", (*MainController).Home), + web.NSRouter("/user", uc), + web.NSGet("/health", Health), + ) + // register namespace + web.AddNamespace(ns) + web.Run() +} + +type MainController struct { + web.Controller +} + +func (mc *MainController) Home() { + mc.Ctx.WriteString("this is home") +} + +type UserController struct { + web.Controller +} + +func (uc *UserController) Get() { + uc.Ctx.WriteString("get user") +} + +func Health(ctx *context.Context) { + ctx.WriteString("health") +} +``` + +You can accessing these URLs: + +- `GET http://localhost:8080/v1/home` +- `GET http://localhost:8080/v1/user` +- `GET http://localhost:8080/v1/health` + +The rule for these addresses can be summarized as concat. For example, in this example our `namespace` is prefixed with `v1`, so it is a `v1` segment before the registered route. + +Notice that inside the `main` function, we use a different way to register routes. It can be said that either [functional route registration](./functional_style/README.md) or [controller route registration](./ctrl_style/README.md) is OK for `namespace`. + +## Nested Namespace + +Sometimes we want `namespace` to be nested inside `namespace`. In this case we can use the `web.NSNamespace` method to inject a child `namespace`. + +Example: + +```go +func main() { + uc := &UserController{} + // initiate namespace + ns := web.NewNamespace("/v1", + web.NSCtrlGet("/home", (*MainController).Home), + web.NSRouter("/user", uc), + web.NSGet("/health", Health), + // 嵌套 namespace + web.NSNamespace("/admin", + web.NSRouter("/user", uc), + ), + ) + // register namespace + web.AddNamespace(ns) + web.Run() +} +``` + +Start the service, and access `GET http://localhost:8080/v1/admin/user`, you can see the output. + +## Conditional Namespace Routes + +Beego's `namespace` provides a conditional judgment mechanism. Routes registered under the `namespace` will be executed only if the conditions are met. Essentially, this is just a `filter` application. + +For example, we want the user's request to have an `x-trace-id` in the header of the request in order to be processed by subsequent requests: + +```go +func main() { + uc := &UserController{} + // 初始化 namespace + ns := web.NewNamespace("/v1", + web.NSCond(func(b *context.Context) bool { + return b.Request.Header["x-trace-id"][0] != "" + }), + web.NSCtrlGet("/home", (*MainController).Home), + web.NSRouter("/user", uc), + web.NSGet("/health", Health), + // 嵌套 namespace + web.NSNamespace("/admin", + web.NSRouter("/user", uc), + ), + ) + //注册 namespace + web.AddNamespace(ns) + web.Run() +} +``` + +In general, we don't recommend using this feature now either, because its functionality overlaps with `filter`. We recommend that you should consider implementing a `filter` normally yourself if you need to, the code will be more understandable. This feature will be considered to be replaced by a `filter` in a future version, and the method will be removed later. + +## Filter + +`namespace` also supports `filter`. The `filter` will only work on routes registered under this `namespace`, and will have no effect on other routes. + +We have two ways to add `Filter`, one is in `NewNamespace`, calling `web.NSBefore` or `web.NSAfter`, or we can call `ns.Filter()` + +```go +func main() { + uc := &UserController{} + // initiate namespace + ns := web.NewNamespace("/v1", + web.NSCond(func(b *context.Context) bool { + return b.Request.Header["x-trace-id"][0] != "" + }), + web.NSBefore(func(ctx *context.Context) { + fmt.Println("before filter") + }), + web.NSAfter(func(ctx *context.Context) { + fmt.Println("after filter") + }), + web.NSCtrlGet("/home", (*MainController).Home), + web.NSRouter("/user", uc), + web.NSGet("/health", Health), + // nested namespace + web.NSNamespace("/admin", + web.NSRouter("/user", uc), + ), + ) + + ns.Filter("before", func(ctx *context.Context) { + fmt.Println("this is filter for health") + }) + // register namespace + web.AddNamespace(ns) + web.Run() +} +``` + +Currently, `namespace` has limited support for `filter`, only `before` and `after`. + +More details refer to [Filter](../filter/README.md) + +## NSInclude + +Next we discuss something a bit strange, the `web.NSInclude` method. This method is a companion method to [annotate/commente routes](./ctrl_style/README.md) companion method. + +It only works for annotate/comment routes. + +Example: + +```go +func init() { + api := web.NewNamespace("/api/v1", + web.NSNamespace("/goods", + web.NSInclude( + &controllers.GoodsController{}, + ), + ), + ) + web.AddNamespace(api) +} +``` + +Notice that our `GoodsController` here is necessarily a `Controller` that annotates routes and has been generated using the [`bee` command](./bee/README.md) to generate the annotated routes. + +## Reference + +[functional style](./functional_style/README.md) +[controller style](./ctrl_style/README.md) +[filter](../filter/README.md) +[annotate/comment routes](./ctrl_style/README.md) diff --git a/docs/en-US/v2.3.x/web/router/router_rule.md b/docs/en-US/v2.3.x/web/router/router_rule.md new file mode 100644 index 0000000..b39d454 --- /dev/null +++ b/docs/en-US/v2.3.x/web/router/router_rule.md @@ -0,0 +1,144 @@ +--- +title: Route Rules +lang: en-US +--- + +# Route Rules + +Routing rules means, when we register a route, what kind of requests will be processed? And, if my request path contains parameters, how do I get the values? + +First, we will match the `http` method first. For example, if you register only the `get` method for the path `api/user/*`, then that means that only `get` requests will be processed. + +Second, after the `http` method is matched, we will further match the path. + +In addition, in order to facilitate you to write the right route quickly, we are soliciting various routing rules to write here, please submit PR directly to github, attached to the last section of this page. + +## Details + +### Fixed Routes + +For example `/api/user/update` means that only the request path is `http://your.com/api/user/update` will be matched, while something like `http://your.com/api/user/update/aa` will not be matched. + +### `*` Routes + +Example for route `/api/user/name/*`, all these path matched: +- `/api/user/name` +- `/api/user/name/tom` +- `/api/user/name/jerry/home` + +That is, all path has prefix `/api/user/name` matched. + +Another example `/api/*/name`, all these path matched: + +- `/api/tom/name` +- `/api/tom/jerry/name` + +This means that as long as there is at least one non-empty segment between `api` and `name`, it's matched. + +So all these path will not be matched: + +- `/api//name` +- `/api/name` + +Be especially careful when `*` appears in the middle. For example when we have registered two routes `/api/*/name` and `/api/*/mid/name` at the same time: + +- `/api/tom/mid/name` matches `/api/*/mid/name` +- `/api/mid/name` matches `/api/*/name` + +We allow multiple `*` for example, `/api/*/name/*/detail`, all these path matched: + +- `/api/tom/name/profile/detail` +- `/api/jerry/name/order/detail` + +Practically speaking, we don't recommend using multiple `*`, it reflects the fact that the API is not really well designed. Under normal circumstances, it should only appear at the end. + +Generally speaking appears in the middle section, mostly because this place is actually a parameter, such as the common RESTFul API in the form of routing: `/api/order/:id/detail`. It is the same as `/api/order/*/detail` in terms of implementation, but the former registers the expression of an ID in the middle, which clearly has a meaning. + +Essentially, this kind of routing can be understood as a special kind of regular routing. + +When we use this registration routing method, we can use `:splat` to get the data hit by `*`. + +```go +// web.Router("/user/name/*", ctrl, "post:Post") +func (ctrl *MainController) Post() { + + username := ctrl.Ctx.Input.Param(":splat") + + ctrl.Ctx.WriteString("Your router param username is:" + username) +} +``` + +If it is multi-segment, e.g. `/api/*/name/*/detail`, then `:splat` can only get the data of the last segment: + +```go +// web.Router("/api/*/name/*/detail", ctrl, "post:Post") +func (ctrl *MainController) Post() { + + username := ctrl.Ctx.Input.Param(":splat") + + ctrl.Ctx.WriteString("Your router param username is:" + username) +} +``` + +If accessing `http://localhost:8080/api/tom/name/oid123/detail`,we got `username=oid123 + +In summary, if you want to use `*`, we recommend that you should have only one `*` inside the whole route, and also try to avoid including parameter routes or regular routes. And the contents of `*` hits can be obtained by `:splat`. + +### Path Variable Routes + +The syntax is `:` followed by the name of the parameter in the path. + +For example, `/api/:username/profile`。The route `:username` means that the data located between `api` and `profile` is the username, these path matched: + +- `/api/flycash/profile` +- `/api/astaxie/profile` +- `/api/123456/profile` + +But these not: + +- `/api//profile` +- `/api/tom/jerry/profile` + +You can get the value of `username` by: + +```go +func (ctrl *MainController) Get() { + + username := ctrl.Ctx.Input.Param(":username") + + ctrl.Ctx.WriteString("Your router param username is:" + username) +} +``` + +For `/api/:id`, `api/123` matches while `api/` not. If we want `api/` matched, we need to use route `/api/?:id` + +### Regular Expression Route + +Beego supports regular expression routes. This is a very powerful feature. In fact, the parameter routing we mentioned earlier can also be understood as a kind of regular expression routes. But the above parameter route is very much used in practice, so we list it separately in the documentation. + +The syntax is `:param(reg)`, And `param` is the parameter name and you can obtain the value by `Ctx.Input.Param(":param")`. `reg` is the regular expression. + +For example `/api/:id([0-9]+)`: + +- `/api/123`: `id=123` +- `/api/tom`: DO NOT match + +There are some builtin regular expression routes: + +- `/:id:int`: `:int` equals to `[0-9]+`; +- `/:hi:string`: `:string` equals to `[\w]+`; + +There are also some more complex uses of regular expressions, and we don't really recommend using these: + +- `/cms_:id([0-9]+).html` +- `/download/\*.\*`: for example `/download/file/api.xml` matched, and `:path=file/api`, `:ext=xml` + +However, being powerful means that learning costs are higher. We strongly recommend that you try to avoid using this type of complex routing, which is equivalent to leaking some of the business logic into the route registration, which is not a good design in itself. + +## Examples + +If you have written interesting routing rules, please submit them here so that they can help more people. :) + +## Reference + +[Input](../input/README.md) diff --git a/docs/en-US/v2.3.x/web/router/router_tree.md b/docs/en-US/v2.3.x/web/router/router_tree.md new file mode 100644 index 0000000..65de675 --- /dev/null +++ b/docs/en-US/v2.3.x/web/router/router_tree.md @@ -0,0 +1,63 @@ +--- +title: Print Routes +lang: en-US +--- + +# Print Routes + +Using =`web.PrintTree()` to print all routes: + +```go +package main + +import ( + "fmt" + "github.com/beego/beego/v2/server/web" +) + +type UserController struct { + web.Controller +} + +func (u *UserController) HelloWorld() { + u.Ctx.WriteString("hello, world") +} + +func main() { + web.BConfig.RouterCaseSensitive = false + web.AutoRouter(&UserController{}) + tree := web.PrintTree() + methods := tree["Data"].(web.M) + for k, v := range methods { + fmt.Printf("%s => %v\n", k, v) + } +} +``` + +If you register a route that uses `*` as a method, which means it matches any HTTP method, then it will print out one for each method. The `AutoRouter` is the one that matches any HTTP method, so it will end up printing out a bunch of things. + +```shell +MKCOL => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +CONNECT => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +POST => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +UNLOCK => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +PROPFIND => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +PATCH => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +GET => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +DELETE => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +PROPPATCH => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +COPY => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +OPTIONS => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +HEAD => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +LOCK => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +PUT => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +TRACE => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +MOVE => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]] +``` + +Let's use `POST => &[[/user/helloworld/* map[*:HelloWorld] main.UserController]` as an example to show how to interpret. +It means that the POST method accesses the path of the pattern `/user/helloworld/*`, then it will execute the `HelloWorld` method inside `main.UserController`. + +## Reference + +- [admin service](../admin/README.md) diff --git a/docs/en-US/v2.3.x/web/session/README.md b/docs/en-US/v2.3.x/web/session/README.md new file mode 100644 index 0000000..60c73bd --- /dev/null +++ b/docs/en-US/v2.3.x/web/session/README.md @@ -0,0 +1,301 @@ +--- +title: Session +lang: en-US +--- + +# Session + +`beego` has a built-in `session` module. Currently, the backend engines supported by the `session` module include `memory`, `cookie`, `file`, `mysql`, `redis`, `couchbase`, `memcache`, `postgres`, and users can also implement their own engines according to the corresponding interfaces. + +## Web Session + +Example + +```go +web.BConfig.WebConfig.Session.SessionOn = true +``` + +Or in configuration file: + +``` +sessionon = true +``` + +And then you can use session: + +```go +func (this *MainController) Get() { + v := this.GetSession("asta") + if v == nil { + this.SetSession("asta", int(1)) + this.Data["num"] = 0 + } else { + this.SetSession("asta", v.(int)+1) + this.Data["num"] = v.(int) + } + this.TplName = "index.tpl" +} +``` + +`session` contains APIs: + +- `SetSession(name string, value interface{})` +- `GetSession(name string) interface{}` +- `DelSession(name string)` +- `SessionRegenerateID()` +- `DestroySession()` + +Or you can get the entire session instance: + +```go +sess := this.StartSession() +defer sess.SessionRelease() +``` + +`sess` contains APIs: + +- `sess.Set()` +- `sess.Get()` +- `sess.Delete()` +- `sess.SessionID()` +- `sess.Flush()` + +But we still recommend you to use `SetSession, GetSession, DelSession` three methods to operate, to avoid the problem of not releasing resources in the process of their own operations. + +Some parameters: + +- `web.BConfig.WebConfig.Session.SessionOn`: Set whether to open `Session`, the default is `false`, the corresponding parameter name of the configuration file: `sessionon` + +- `web.BConfig.WebConfig.Session.SessionProvider`: Set the engine of `Session`, the default is `memory`, currently there are `file`, `mysql`, `redis`, etc., the configuration file corresponds to the parameter name: `sessionprovider`. + +- `web.BConfig.WebConfig.Session.SessionName`: Set the name of `cookies`, `Session` is saved in the user's browser `cookies` by default, the default name is `beegosessionID`, the corresponding parameter name of the configuration file is: `sessionname`. + +- `web.BConfig.WebConfig.Session.SessionGCMaxLifetime`: Set the `Session` expiration time, the default value is `3600` seconds, the corresponding parameter of the configuration file: `sessiongcmaxlifetime`. + +- `web.BConfig.WebConfig.Session.SessionProviderConfig`: Set the save path or address of the corresponding `file`, `mysql`, `redis` engines, the default value is empty, and the corresponding parameter of the configuration file: `sessionproviderconfig`. + +- `web.BConfig.WebConfig.Session.SessionHashFunc`: Default value is `sha1`, using `sha1` encryption algorithm to produce `sessionid` + +- `web.BConfig.WebConfig.Session.SessionCookieLifeTime`: Set the expiration time for `cookie`, which is used to store data stored on the client. + +When using a particular engine, you need to anonymously introduce the package corresponding to that engine to complete the initialization work: + +```go +import _ "github.com/beego/beego/v2/server/web/session/mysql" +``` + +### Engines + +#### File + +When `SessionProvider` is `file` `SessionProviderConfig` is the directory where the file is saved, as follows: + +```go +web.BConfig.WebConfig.Session.SessionProvider = "file" +web.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp" +``` + +#### MySQL + +When `SessionProvider` is `mysql`, `SessionProviderConfig` is the address, using [go-sql-driver](https://github.com/go-sql-driver/mysql), as follows: + +```go +web.BConfig.WebConfig.Session.SessionProvider = "mysql" +web.BConfig.WebConfig.Session.SessionProviderConfig = "username:password@protocol(address)/dbname?param=value" +``` + +It should be noted that when using `mysql` to store `session` information, you need to create a table in `mysql` beforehand, and the table creation statement is as follows: + +```sql +CREATE TABLE `session` ( + `session_key` char(64) NOT NULL, + `session_data` blob, + `session_expiry` int(11) unsigned NOT NULL, + PRIMARY KEY (`session_key`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +``` + +#### Redis + +When `SessionProvider` is `redis` `, SessionProviderConfig` is the address of `redis`, using [redigo](https://github.com/garyburd/redigo), as follows: + +```go +web.BConfig.WebConfig.Session.SessionProvider = "redis" +web.BConfig.WebConfig.Session.SessionProviderConfig = "127.0.0.1:6379" +``` + +#### memcache + +When `SessionProvider` is `memcache``, SessionProviderConfig` is the address of `memcache`, using [memcache](https://github.com/beego/memcache), as follows: + +```go +web.BConfig.WebConfig.Session.SessionProvider = "memcache" +web.BConfig.WebConfig.Session.SessionProviderConfig = "127.0.0.1:7080" +``` + +#### Postgres + +When `SessionProvider` is `postgres` `, SessionProviderConfig` is the address of `postgres`, using [postgres](https://github.com/lib/pq), as follows: + +```go +web.BConfig.WebConfig.Session.SessionProvider = "postgresql" +web.BConfig.WebConfig.Session.SessionProviderConfig = "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full" +``` + +#### Couchbase + +When `SessionProvider` is `couchbase` `, SessionProviderConfig` is the **address** of `couchbase`, using [couchbase](https://github.com/couchbaselabs/go- couchbase), as follows: + +```go +web.BConfig.WebConfig.Session.SessionProvider = "couchbase" +web.BConfig.WebConfig.Session.SessionProviderConfig = "http://bucketname:bucketpass@myserver:8091" +``` + +### Notices + +Because `session` uses `gob` to register stored objects, such as `struct`, if you use a non-`memory` engine, please register these structures in `init` of `main.go` yourself, otherwise it will cause an unresolvable error after application restart + +## Using Session Without Web Module + +Import the module: + +```go +import ( + "github.com/beego/beego/v2/server/web/session" +) +``` + +Initiate the manager instance: + +```go +var globalSessions *session.Manager +``` + +Next, initialize the data in your entry function: + +```go +func init() { + sessionConfig := &session.ManagerConfig{ + CookieName:"gosessionid", + EnableSetCookie: true, + Gclifetime:3600, + Maxlifetime: 3600, + Secure: false, + CookieLifeTime: 3600, + ProviderConfig: "./tmp", + } + globalSessions, _ = session.NewManager("memory",sessionConfig) + go globalSessions.GC() +} +``` + +Parameters of NewManager: + +1. Saving provider name: memory, file, mysql, redis +2. A JSON string that contains the config information. + 1. cookieName: Cookie name of session id saved on the client + 2. enableSetCookie, omitempty: Whether to enable SetCookie, omitempty + 3. gclifetime: The interval of GC. + 4. maxLifetime: Expiration time of data saved on the server + 5. secure: Enable https or not. There is `cookie.Secure` while configure cookie. + 6. sessionIDHashFunc: SessionID generator function. `sha1` by default. + 7. sessionIDHashKey: Hash key. + 8. cookieLifeTime: Cookie expiration time on the client. 0 by default, which means life time of browser. + 9. providerConfig: Provider-specific config. See below for more information. + +Then we can use session in our code: + + func login(w http.ResponseWriter, r *http.Request) { + sess, _ := globalSessions.SessionStart(w, r) + defer sess.SessionRelease(w) + username := sess.Get("username") + if r.Method == "GET" { + t, _ := template.ParseFiles("login.gtpl") + t.Execute(w, nil) + } else { + sess.Set("username", r.Form["username"]) + } + } + +Here are the methods of globalSessions: + +- `SessionStart` Return session object based on current request. +- `SessionDestroy` Destroy current session object. +- `SessionRegenerateId` Regenerate a new sessionID. +- `GetActiveSession` Get active session user. +- `SetHashFunc` Set sessionID generator function. +- `SetSecure` Enable Secure cookie or not. + +The returned session object is an Interface. Here are the methods: + +- `Set(ctx context.Context, key, value interface{}) error` +- `Get(ctx context.Context, key interface{}) interface{}` +- `Delete(ctx context.Context, key interface{}) error` +- `SessionID() string` +- `SessionRelease(ctx context.Context, w http.ResponseWriter)` +- `SessionReleaseIfPresent(ctx context.Context, w http.ResponseWriter)` +- `Flush(ctx context.Context) error` + +Please note that `SessionRelease` and `SessionReleaseIfPresent` are used to release session resources. +`SessionReleaseIfPresent` releases session resources only if the session exists. Not all engines support this feature, so you need to check if the specific implementation supports it. +`SessionRelease` releases session resources regardless of whether the session exists, except for the `mysql`, `postgres`, and `mem` engines. The `mysql` and `postgres` engines release session resources only if the session exists, while the `mem` engine automatically releases session resources when `Set`, `Delete`, or `Flush` is called. + +## Engines setting + +We've already seen configuration of `memory` provider. Here is the configuration of the others: + +- `mysql`: + + All the parameters are the same as memory's except the fourth parameter, e.g.: + + username:password@protocol(address)/dbname?param=value + + For details see the [go-sql-driver/mysql](https://github.com/go-sql-driver/mysql#dsn-data-source-name) documentation. + +- `redis`: + + Connection config: address,pool,password + + 127.0.0.1:6379,100,astaxie + +- `file`: + + The session save path. Create new files in two levels by default. E.g.: if sessionID is `xsnkjklkjjkh27hjh78908` the file will be saved as `./tmp/x/s/xsnkjklkjjkh27hjh78908` + + ./tmp + +## Creating a new provider + +Sometimes you need to create your own session provider. The Session module uses interfaces, so you can implement this interface to create your own provider easily. + +```go +// Store contains all data for one session process with specific id. +type Store interface { + Set(ctx context.Context, key, value interface{}) error // Set set session value + Get(ctx context.Context, key interface{}) interface{} // Get get session value + Delete(ctx context.Context, key interface{}) error // Delete delete session value + SessionID(ctx context.Context) string // SessionID return current sessionID + SessionReleaseIfPresent(ctx context.Context, w http.ResponseWriter) // SessionReleaseIfPresent release the resource & save data to provider & return the data when the session is present, not all implementation support this feature, you need to check if the specific implementation if support this feature. + SessionRelease(ctx context.Context, w http.ResponseWriter) // SessionRelease release the resource & save data to provider & return the data + Flush(ctx context.Context) error // Flush delete all data +} + +// Provider contains global session methods and saved SessionStores. +// it can operate a SessionStore by its id. +type Provider interface { + SessionInit(ctx context.Context, gclifetime int64, config string) error + SessionRead(ctx context.Context, sid string) (Store, error) + SessionExist(ctx context.Context, sid string) (bool, error) + SessionRegenerate(ctx context.Context, oldsid, sid string) (Store, error) + SessionDestroy(ctx context.Context, sid string) error + SessionAll(ctx context.Context) int // get all active session + SessionGC(ctx context.Context) +} +``` + +Finally, register your provider: + + func init() { + // ownadapter is an instance of session.Provider + session.Register("own", ownadapter) + } diff --git a/docs/en-US/v2.3.x/web/view/README.md b/docs/en-US/v2.3.x/web/view/README.md new file mode 100644 index 0000000..acfe360 --- /dev/null +++ b/docs/en-US/v2.3.x/web/view/README.md @@ -0,0 +1,372 @@ +--- +title: Template Engine +lang: en-US +--- + +# Template Engine + +Beego uses Go's built-in package `html/template` as the template parser. Upon startup, it will compile and cache the templates into a map for efficient rendering. + +## Template Directory + +The default template directory for Beego is `views`. Template files can be put into this directory and Beego will parse and cache them automatically. However, if the development mode is enabled, Beego parses templates every time without caching. Beego can only have one template directory which can be customized: + +``` +web.BConfig.WebConfig.ViewsPath = "myviewpath" +``` + +You can add alternative template directories by calling + +``` +web.AddViewPath("moreViews") +``` + +This will parse and cache template files in this directory and allow you to use them by setting ViewPath on a Controller: + +``` +this.ViewPath = "moreViews" +``` + +Setting a ViewPath to a directory which was not previously registered with AddViewPath() will fail with "Unknown view path" + + +## Auto Rendering + +You don't need to render and output templates manually. Beego will call Render automatically after finishing the method. You can disable auto rendering in the configuration file or in `main.go` if you don't need it. + +In configuration file: + +``` +autorender = false +``` + +In main.go: + +``` +web.BConfig.WebConfig.AutoRender = false +``` + +## Template Tags + +Go uses {{ and }} as the default template tags. In the case that these tags conflict with other template tags as in AngularJS, we can use other tags. To do so, +In configuration file: + +``` + templateleft = <<< + templateright = >>> +``` + + +Or, add these to the main.go: + +``` + web.BConfig.WebConfig.TemplateLeft = "<<<" + web.BConfig.WebConfig.TemplateRight = ">>>" +``` + +## Template Data + +Template gets its data from `this.Data` in Controller. So for example if you write +``` +{{.Content}} +``` +in the template, you need to assign it in the Controller first: + +``` +this.Data["Content"] = "value" +``` + +Different rendering types: + +### struct + +Struct variable: + +``` + type A struct{ + Name string + Age int + } +``` + +Assign value in the Controller: + +``` +this.Data["a"]=&A{Name:"astaxie",Age:25} +``` + +Render it in the template: + +``` + the username is {{.a.Name}} + the age is {{.a.Age}} +``` +### map + +Assign value in the Controller: + +``` + mp["name"]="astaxie" + mp["nickname"] = "haha" + this.Data["m"]=mp +``` + +Render it in the template: + +``` + the username is {{.m.name}} + the username is {{.m.nickname}} +``` + +### slice + +Assign value in the Controller: + +``` + ss :=[]string{"a","b","c"} + this.Data["s"]=ss +``` + +Render it in the template: + +``` + {{range $key, $val := .s}} + {{$key}} + {{$val}} + {{end}} +``` + +## Template Name + +> From version 1.6: this.TplNames is this.TplName + +Beego uses Go's built-in template engine, so the syntax is same as Go. To learn more about template see [Templates](https://github.com/Unknwon/build-web-application-with-golang_EN/blob/master/eBook/07.4.md). + +You can set the template name in Controller and Beego will find the template file under the viewpath and render it automatically. In the config below, Beego will find add.tpl under admin and render it. + +```go +this.TplName = "admin/add.tpl" +``` + +Beego supports `.tpl` and `.html` file extensions by default. If you're using other extensions, you must set it in the configuration first: + +```go +beego.AddTemplateExt("file_extension_you_need") +``` + +If `TplName` is not set in the Controller while `autorender` is enabled, Beego will use `TplName` as below by default: + +```go +c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt +``` + +It is Controller name + "/" + request method name + "." + template extension. So if the Controller name is `AddController`, request method is `POST` and the default template extension is `tpl`, Beego will render `/viewpath/addcontroller/post.tpl` template file. + +## Layout Design + +Beego supports layout design. For example, if in your application the main navigation and footer does not change and only the content part is different, you can use a layout like this: + +```go +this.Layout = "admin/layout.html" +this.TplName = "admin/add.tpl" +``` + +In `layout.html` you must set a variable like this: + +``` +{{.LayoutContent}} +``` + +Beego will parse the file named `TplName` and assign it to `LayoutContent` then render `layout.html`. + +Beego will cache all the template files. You can also implement a layout this way: + +``` + {{template "header.html"}} + Logic code + {{template "footer.html"}} +``` + +## LayoutSection + +`LayoutContent` is a little complicated, as it can include Javascript and CSS. Since in most situations having only one `LayoutContent` is not enough, there is an attribute called `LayoutSections` in `Controller`. It allows us to set multiple `section` in `Layout` page and each `section` can contain its own sub-template page. + +layout_blog.tpl: + +``` + + + + Lin Li + + + + + {{.HtmlHead}} + + + +
+ {{.LayoutContent}} +
+
+ {{.SideBar}} +
+ + + {{.Scripts}} + + +``` + +html_head.tpl: + +``` + +``` + +scripts.tpl: + +``` + +``` + +Here is the logic in the Controller: + +```go +type BlogsController struct { +web.Controller +} + +func (this *BlogsController) Get() { +this.Layout = "layout_blog.tpl" +this.TplName = "blogs/index.tpl" +this.LayoutSections = make(map[string]string) +this.LayoutSections["HtmlHead"] = "blogs/html_head.tpl" +this.LayoutSections["Scripts"] = "blogs/scripts.tpl" +this.LayoutSections["Sidebar"] = "" +} +``` + +## Another approach + +We can also just specify the template the controller is going to use and let the template system handle the layout: + +for example: + +controller: + +```go +this.TplName = "blog/add.tpl" +this.Data["SomeVar"] = "SomeValue" +this.Data["Title"] = "Add" +``` + +template add.tpl: + +``` + {{ template "layout_blog.tpl" . }} + {{ define "css" }} + + {{ end}} + + + {{ define "content" }} +

{{ .Title }}

+

This is SomeVar: {{ .SomeVar }}

+ {{ end }} + + {{ define "js" }} + + {{ end}} +``` + + +layout_blog.tpl: + +``` + + + + {{ .Title }} + + + + + {{ block "css" . }}{{ end }} + + + +
+ {{ block "content" . }}{{ end }} +
+ + + {{ block "js" . }}{{ end }} + + +``` + +Using `block` action instead of `template` allows us to have default block content and skipping blocks that we don't need in every template (for example, if we don't need css block in `add.tpl` template - we will not define it and that won't raise an error) + + +## renderform + +Define struct: + +```go +type User struct { +Id int `form:"-"` +Name interface{} `form:"username"` +Age int `form:"age,text,age:"` +Sex string +Intro string `form:",textarea"` +} +``` + +- StructTag definition uses `form` as tag. There are three optional params separated by ',': + - The first param is `name` attribute of the form field. If empty, it will use `struct field name` as the value. + - The second param is the form field type. If empty, it is assumed as `text`. + - The third param is the tag of form field. If empty, it will use `struct field name` as the tag name. + +- If the `form` tag only has one value, it is the `name` attribute of the form field. Except last value can be ignore all the other place must be separated by ','. E.g.: `form:",,username:"` + +- To ignore a field there are two ways: + - The first way is to use lowercase for the field name in the struct. + - The second way is to set `-` as the value of `form` tag. + +controller: + +```go +func (this *AddController) Get() { +this.Data["Form"] = &User{} +this.TplName = "index.tpl" +} +``` + +The param of Form must be a pointer to a struct. + +template: + +``` +
+ {{.Form | renderform}} +
+``` + +The code above will generate the form below: + +``` +Name:
+Age:
+Gender:
+Intro: +``` diff --git a/docs/en-US/v2.3.x/web/view/func.md b/docs/en-US/v2.3.x/web/view/func.md new file mode 100644 index 0000000..24dbd15 --- /dev/null +++ b/docs/en-US/v2.3.x/web/view/func.md @@ -0,0 +1,88 @@ +--- +title: Template Functions +lang: en-US +--- + +# Template Functions + +Beego supports custom template functions but it must be set as below before `web.Run()`: +``` + func hello(in string)(out string){ + out = in + "world" + return + } + + web.AddFuncMap("hi",hello) +``` + +Then you can use these functions in template: +``` + {{.Content | hi}} +``` +Here are Beego's built-in template functions: +- dateformat: Format time, return string. + ``` + {{dateformat .Time "2006-01-02T15:04:05Z07:00"}} + ``` +- date: This is similar to date function in PHP. It can easily return time by string + ``` + {{date .T "Y-m-d H:i:s"}} + ``` +- compare: Compare two objects. If they are the same return true otherwise return false. + ``` + {{compare .A .B}} + ``` +- substr: Return sub string. supports UTF-8 string. + ``` + {{substr .Str 0 30}} + ``` +- html2str: Parse html to string by removing tags like script and css and return text. + ``` + {{html2str .Htmlinfo}} + ``` +- str2html: Parse string to HTML, no escaping. + ``` + {{str2html .Strhtml}} + ``` +- htmlquote: Escaping html. + ``` + {{htmlquote .quote}} + ``` +- htmlunquote: Unescaping to html. + ``` + {{htmlunquote .unquote}} + ``` +- renderform: Generate form from StructTag. + ``` + {{&struct | renderform}} + ``` +- assets_js: Create a ` + + {{.Scripts}} + + +``` + +`html_head.tpl`: + +```html + +``` + +`scripts.tpl`: + +```html + +``` + +逻辑处理如下所示: + +```go +type BlogsController struct { + web.Controller +} + +func (this *BlogsController) Get() { + this.Layout = "layout_blog.tpl" + this.TplName = "blogs/index.tpl" + this.LayoutSections = make(map[string]string) + this.LayoutSections["HtmlHead"] = "blogs/html_head.tpl" + this.LayoutSections["Scripts"] = "blogs/scripts.tpl" + this.LayoutSections["Sidebar"] = "" +} +``` + +## renderform 使用 + +定义 struct: + +```go +type User struct { + Id int `form:"-"` + Name interface{} `form:"username"` + Age int `form:"age,text,年龄:"` + Sex string + Intro string `form:",textarea"` +} +``` + +- StructTag 的定义用的标签用为 `form`,和 [ParseForm 方法](../controller/params.md#%E7%9B%B4%E6%8E%A5%E8%A7%A3%E6%9E%90%E5%88%B0-struct) 共用一个标签,标签后面有三个可选参数,用 `,` 分割。第一个参数为表单中类型的 `name` 的值,如果为空,则以 `struct field name` 为值。第二个参数为表单组件的类型,如果为空,则为 `text`。表单组件的标签默认为 `struct field name` 的值,否则为第三个值。 +- 如果 `form` 标签只有一个值,则为表单中类型 `name` 的值,除了最后一个值可以忽略外,其他位置的必须要有 `,` 号分割,如:`form:",,姓名:"` +- 如果要忽略一个字段,有两种办法,一是:字段名小写开头,二是:`form` 标签的值设置为 `-` +- 现在的代码版本只能实现固定的格式,用 br 标签实现换行,无法实现 css 和 class 等代码的插入。所以,要实现 form 的高级排版,不能使用 renderform 的方法,而需要手动处理每一个字段。 + +controller: + +```go +func (this *AddController) Get() { + this.Data["Form"] = &User{} + this.TplName = "index.tpl" +} +``` + +Form 的参数必须是一个 struct 的指针。 + +template: + +```html +
{{.Form | renderform}}
+``` + +上面的代码生成的表单为: + +```html + Name:
+ 年龄:
+ Sex:
+ Intro: +``` diff --git a/docs/zh/v2.3.x/web/view/func.md b/docs/zh/v2.3.x/web/view/func.md new file mode 100644 index 0000000..9157172 --- /dev/null +++ b/docs/zh/v2.3.x/web/view/func.md @@ -0,0 +1,152 @@ +--- +title: 模板函数 +lang: zh +--- + +# 模板函数 + +Beego 支持用户定义模板函数,但是必须在 `web.Run()` 调用之前,设置如下: + +```go +func hello(in string)(out string){ + out = in + "world" + return +} + +web.AddFuncMap("hi",hello) +``` + +定义之后你就可以在模板中这样使用了: + +```html +{{.Content | hi}} +``` + +目前 Beego 内置的模板函数如下所示: + +- dateformat + + 实现了时间的格式化,返回字符串,使用方法 + + ```html + {{dateformat .Time "2006-01-02T15:04:05Z07:00"}} + ``` + +- date + + 实现了类似 PHP 的 date 函数,可以很方便的根据字符串返回时间,使用方法: + + ```html + {{date .T "Y-m-d H:i:s"}} + ``` + +- compare + + 实现了比较两个对象的比较,如果相同返回 true,否者 false,使用方法 + + ```html + {{compare .A .B}} + ``` + +- substr + + 实现了字符串的截取,支持中文截取的完美截取,使用方法 + + ```html + {{substr .Str 0 30}} + ``` + +- html2str + + 实现了把 html 转化为字符串,剔除一些 script、css 之类的元素,返回纯文本信息,使用方法 + + ```html + {{html2str .Htmlinfo}} + ``` + +- str2html + + 实现了把相应的字符串当作 HTML 来输出,不转义,使用方法 + + ```html + {{str2html .Strhtml}} + ``` + +- htmlquote + + 实现了基本的 html 字符转义,使用方法 + + ```html + {{htmlquote .quote}} + ``` + +- htmlunquote + + 实现了基本的反转移字符,使用方法 + + ```html + {{htmlunquote .unquote}} + ``` + +- renderform + + 根据 StructTag 直接生成对应的表单,使用方法 + + ```html + {{&struct | renderform}} + ``` + +- assets_js + + 为 js 文件生成一个 `