Compare commits

...

31 Commits

Author SHA1 Message Date
0b6798acb5 add Quick mode for gtimer (#2488) 2023-03-20 10:00:55 +08:00
e721124b6c support microseconds resolution for created_at/updated_at/deleted_at (#2512) 2023-03-20 09:57:37 +08:00
b32eb30212 feat: improve polaris register and upgrade polaris-go version v1.3.0 (#2524) 2023-03-20 09:52:58 +08:00
967a39ecbe feat:upgrade polairs-go sdk version v1.3.0 for config (#2525)
feat:upgrade polairs-go sdk version v1.3.0
2023-03-20 09:52:19 +08:00
dfb7f5abfb improve error message when the router hander definition is not standrad (#2528) 2023-03-20 09:51:43 +08:00
56f5d5125b fix quick exit when double click cli to install/upgrade (#2521) 2023-03-17 17:56:41 +08:00
5083174a92 fix goreport for gofmt (#2523) 2023-03-17 17:54:14 +08:00
4a278dfd79 improve grpcx (#2522) 2023-03-17 17:51:10 +08:00
80d57ed8f9 add postForm with multi data content type support (#2508) 2023-03-14 09:57:22 +08:00
b742e222d6 improve command gen pb by adding controller go files generating (#2518) 2023-03-14 09:47:42 +08:00
ae86f66545 fix issue #2499 #2515 (#2517) 2023-03-13 22:16:57 +08:00
45e4c9e16c add tag value of in support for api definition that has meta info (#2450) 2023-03-13 19:29:30 +08:00
XG
8c07f1a42c feat: support graceful shutdown (#2469) (#2475) 2023-03-13 19:21:56 +08:00
e6c97410ef fix: cli gen service:BUG #2310 (#2485) 2023-03-13 19:17:23 +08:00
b1a55c7a32 improvement for grpcx (#2510) 2023-03-13 18:56:19 +08:00
1cd1449085 add package contrib/rpc/grpcx (#2169) 2023-03-08 14:12:51 +08:00
55690f3738 improve unit testing cases coverage to 80%+ (#2480) 2023-03-07 21:27:23 +08:00
13f6fb1929 fix issue struct attribute converting when has no json name but with omitempty tag (#2486) 2023-03-07 21:26:32 +08:00
e8088a6563 improve command gen dao by removing all dao files if clear option is true (#2502)
* improve command  by removing all dao files if clear option is true

* version updates
2023-03-07 19:32:13 +08:00
e8051bad9a fix issue in empty json name along with omitempty tag in package oai (#2500) 2023-03-07 14:17:14 +08:00
d0d41a63a6 robot translator updates (#2491) 2023-03-07 11:01:01 +08:00
853b038a47 add issue translation robot (#2490) 2023-03-06 09:50:03 +08:00
34946f6105 fix configuration of Namespace for pgsql (#2481) 2023-03-01 19:30:40 +08:00
Gin
15d88c269d fix: gredis maxActive config and duplicate connection bug (#2479) 2023-02-27 22:08:37 +08:00
cbbfd85eeb Protect RemoveValue from race condition (#2472) 2023-02-23 19:47:57 +08:00
adf90c876f improve unit testing cases (#2468) 2023-02-23 10:07:40 +08:00
b4f76b8448 fix: use keyword(like: group) as table name in sqlite (#2461) 2023-02-20 22:03:26 +08:00
ed858ebd4b Bump golang.org/x/net from 0.1.0 to 0.7.0 in /cmd/gf (#2460) 2023-02-20 22:02:31 +08:00
272b9c7afd README update for cmd/gf (#2459) 2023-02-20 22:01:36 +08:00
8dc8dd9756 fix issue #2261 (#2458) 2023-02-20 21:57:49 +08:00
a64d1001e2 improve ci by using cache feature of setup-go (#2463) 2023-02-20 21:51:54 +08:00
256 changed files with 9584 additions and 2487 deletions

View File

@ -35,6 +35,15 @@ jobs:
# Service containers to run with `code-test`
services:
# Etcd service.
# docker run -d --name etcd -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes loads/etcd:3.4.24
etcd:
image: loads/etcd:3.4.24
env:
ALLOW_NONE_AUTHENTICATION: yes
ports:
- 2379:2379
# Redis backend server.
redis:
image : loads/redis:7.0
@ -54,7 +63,6 @@ jobs:
MYSQL_DATABASE : test
MYSQL_ROOT_PASSWORD: 12345678
ports:
# Maps tcp port 3306 on service container to the host
- 3306:3306
# PostgreSQL backend server.
@ -103,6 +111,7 @@ jobs:
- 9001:9001
# Polaris backend server.
# docker run -d --name polaris -p 8090:8090 -p 8091:8091 -p 8093:8093 loads/polaris-server-standalone:1.11.2
polaris:
image: loads/polaris-server-standalone:1.11.2
ports:
@ -164,18 +173,8 @@ jobs:
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
- name: Setup Golang caches
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
~/.cache/go-build
~/Library/Caches/go-build
~\AppData\Local\go-build
key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ matrix.go-version }}-
cache: true
cache-dependency-path: '**/go.sum'
- name: Before Script
run: bash .github/workflows/before_script.sh

19
.github/workflows/issue-translator.yml vendored Normal file
View File

@ -0,0 +1,19 @@
# https://github.com/usthe/issues-translate-action
name: 'Issue Translator'
on:
issue_comment:
types: [created]
issues:
types: [opened]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: usthe/issues-translate-action@v2.7
with:
IS_MODIFY_TITLE: true
# not require, default false. Decide whether to modify the issue title
# if true, the robot account @Issues-translate-bot must have modification permissions,
# invite @Issues-translate-bot to your project or use your custom bot.
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿

View File

@ -1,18 +1,20 @@
# GoFrame
<div align=center>
<img src="https://goframe.org/statics/image/gf-head-large.png" width="100"/>
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf)
[![GoFrame CI](https://github.com/gogf/gf/actions/workflows/gf.yml/badge.svg)](https://github.com/gogf/gf/actions/workflows/gf.yml)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf?v=1)](https://goreportcard.com/report/github.com/gogf/gf)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master)
[![Go Report Card](https://goreportcard.com/badge/github.com/gogf/gf/v2)](https://goreportcard.com/report/github.com/gogf/gf/v2)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf)
[![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/gogf/gf)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
</div>
`GoFrame` is a modular, powerful, high-performance and enterprise-class application development framework of Golang.
`GoFrame` is a modular, powerful, high-performance and enterprise-class application development framework of Golang.
# Features
- modular, loosely coupled design
- rich components, out-of-the-box
- automatic codes generating for efficiency
@ -27,34 +29,37 @@
- much, much more...ready to explore?
# Installation
Enter your repo. directory and execute following command:
## primary module
```bash
go get -u -v github.com/gogf/gf/v2
```
## cli tool
```bash
go install github.com/gogf/gf/cmd/gf/v2
```
# Limitation
```
golang version >= 1.15
```
# Architecture
<div align=center>
<img src="https://goframe.org/download/attachments/1114119/arch.png"/>
</div>
# Documentation
* Chinese Official Site(中文官网): [https://goframe.org](https://goframe.org/display/gf)
* GoDoc API: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
- Chinese Official Site(中文官网): [https://goframe.org](https://goframe.org/display/gf)
- GoDoc API: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
# License
@ -76,32 +81,20 @@ golang version >= 1.15
> We list part of the users here, if your company or products are using `GoFrame`, please let us know [here](https://goframe.org/pages/viewpage.action?pageId=1114415).
# Contributors
This project exists thanks to all the people who contribute. [[Contributors](https://github.com/gogf/gf/graphs/contributors)].
<a href="https://github.com/gogf/gf/graphs/contributors"><img src="https://contributors-img.web.app/image?repo=gogf/gf" /></a>
# Donators
If you love `GoFrame`, why not [buy developer a cup of coffee](https://goframe.org/pages/viewpage.action?pageId=1115633)?
# Sponsors
We appreciate any kind of sponsorship for `GoFrame` development. If you've got some interesting, please contact WeChat `389961817` / Email `john@goframe.org`.
# Thanks
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/download/thumbnails/1114119/jetbrains.png" height="120" alt="JetBrains"/></a>
<a href="https://www.atlassian.com/?from=GoFrame"><img src="https://goframe.org/download/attachments/1114119/atlassian.jpg" height="120" alt="Atlassian"/></a>

View File

@ -2,54 +2,67 @@
`gf` is a powerful CLI tool for building [GoFrame](https://goframe.org) application with convenience.
## 1. Install
## 1) PreCompiled Binary
You can also install `gf` tool using pre-built binaries: https://github.com/gogf/gf/releases
You can also install `gf` tool using pre-built binaries: <https://github.com/gogf/gf/releases>
1. `Mac` & `Linux`
```shell
```shell
wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH) && chmod +x gf && ./gf install -y && rm ./gf
```
```
> If you're using `zsh`, you might need rename your alias by command `alias gf=gf` to resolve the conflicts between `gf` and `git fetch`.
2. `Windows`
Manually download, execute in command line it and then follow the instruction.
Manually download, execute it and then follow the instruction.
3. Database support
3. Database `sqlite` and `oracle` are not support in `gf gen` command in default as it needs `cgo` and `gcc`, you can manually make some changes to the source codes and do the building.
| DB | support | remarks |
| :------: | :------: | :------: |
| mysql | yes | - |
| mariadb | yes | - |
| tidb | yes | - |
| mssql | yes | - |
| oracle | yes | - |
| pgsql | yes | - |
| sqlite | yes | - |
| clickhouse | no | manually make some changes to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
| dm | no | manually make some changes to the [source codes](./internal/cmd/cmd_gen_dao.go) and do the building. |
## 2) Manually Install
```shell
```shell
git clone https://github.com/gogf/gf && cd gf/cmd/gf && go install
```
```
## 2. Commands
```html
$ gf
USAGE
gf COMMAND [OPTION]
COMMAND
env show current Golang environment variables
run running go codes with hot-compiled-like feature
gen automatically generate go files for dao/do/entity/pb/pbentity
tpl template parsing and building commands
init create and initialize an empty GoFrame project
pack packing any file/directory to a resource file, or a go file
build cross-building go project for lots of platforms
docker build docker image for current GoFrame project
install install gf binary to system (might need root/admin permission)
version show version information of current binary
env show current Golang environment variables
run running go codes with hot-compiled-like feature
gen automatically generate go files for dao/do/entity/pb/pbentity
tpl template parsing and building commands
init create and initialize an empty GoFrame project
pack packing any file/directory to a resource file, or a go file
build cross-building go project for lots of platforms
docker build docker image for current GoFrame project
install install gf binary to system (might need root/admin permission)
version show version information of current binary
OPTION
-y, --yes all yes for all command without prompt ask
-v, --version show version information of current binary
-d, --debug show internal detailed debugging information
-h, --help more information about this command
-y, --yes all yes for all command without prompt ask
-v, --version show version information of current binary
-d, --debug show internal detailed debugging information
-h, --help more information about this command
ADDITIONAL
Use "gf COMMAND -h" for details about a command.
@ -60,10 +73,3 @@ ADDITIONAL
### 1). Command `gf run` returns `pipe: too many open files`
Please use `ulimit -n 65535` to enlarge your system configuration for max open files for current terminal shell session, and then `gf run`.

View File

@ -39,9 +39,9 @@ require (
go.opentelemetry.io/otel/trace v1.7.0 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/net v0.1.0 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.16.8 // indirect
modernc.org/mathutil v1.4.1 // indirect

View File

@ -79,8 +79,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -97,15 +97,15 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=

View File

@ -4,6 +4,7 @@ import (
"context"
"strings"
"github.com/gogf/gf/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/util/gtag"
@ -48,17 +49,21 @@ func (c cGF) Index(ctx context.Context, in cGFInput) (out *cGFOutput, err error)
_, err = Version.Index(ctx, cVersionInput{})
return
}
answer := "n"
// No argument or option, do installation checks.
if !service.Install.IsInstalled() {
if data, isInstalled := service.Install.IsInstalled(); !isInstalled {
mlog.Print("hi, it seams it's the first time you installing gf cli.")
s := gcmd.Scanf("do you want to install gf binary to your system? [y/n]: ")
if strings.EqualFold(s, "y") {
if err = service.Install.Run(ctx); err != nil {
return
}
gcmd.Scan("press `Enter` to exit...")
answer = gcmd.Scanf("do you want to install gf(%s) binary to your system? [y/n]: ", gf.VERSION)
} else if !data.IsSelf {
mlog.Print("hi, you have installed gf cli.")
answer = gcmd.Scanf("do you want to install gf(%s) binary to your system? [y/n]: ", gf.VERSION)
}
if strings.EqualFold(answer, "y") {
if err = service.Install.Run(ctx); err != nil {
return
}
gcmd.Scan("press `Enter` to exit...")
return
}
// Print help content.
gcmd.CommandFromCtx(ctx).Print()

View File

@ -124,13 +124,16 @@ type cBuildInput struct {
PackSrc string `short:"ps" name:"packSrc" brief:"pack one or more folders into one go file before building"`
PackDst string `short:"pd" name:"packDst" brief:"temporary go file path for pack, this go file will be automatically removed after built" d:"internal/packed/build_pack_data.go"`
ExitWhenError bool `short:"ew" name:"exitWhenError" brief:"exit building when any error occurs, default is false" orphan:"true"`
DumpENV bool `short:"de" name:"dumpEnv" brief:"dump current go build environment before building binary" orphan:"true"`
}
type cBuildOutput struct{}
func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, err error) {
// print used go env
_, _ = Env.Index(ctx, cEnvInput{})
if in.DumpENV {
_, _ = Env.Index(ctx, cEnvInput{})
}
mlog.SetHeaderPrint(true)
@ -239,7 +242,7 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e
if len(customSystems) > 0 && customSystems[0] != "all" && !gstr.InArray(customSystems, system) {
continue
}
for arch, _ := range item {
for arch := range item {
if len(customArches) > 0 && customArches[0] != "all" && !gstr.InArray(customArches, arch) {
continue
}

View File

@ -1,79 +1,7 @@
package cmd
import (
"context"
"fmt"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/genv"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
)
import "github.com/gogf/gf/cmd/gf/v2/internal/cmd/genpb"
type (
cGenPb struct{}
cGenPbInput struct {
g.Meta `name:"pb" brief:"parse proto files and generate protobuf go files"`
}
cGenPbOutput struct{}
cGenPb = genpb.CGenPb
)
func (c cGenPb) Pb(ctx context.Context, in cGenPbInput) (out *cGenPbOutput, err error) {
// Necessary check.
if gproc.SearchBinary("protoc") == "" {
mlog.Fatalf(`command "protoc" not found in your environment, please install protoc first to proceed this command`)
}
// protocol fold checks.
protoFolder := "protocol"
if !gfile.Exists(protoFolder) {
mlog.Fatalf(`proto files folder "%s" does not exist`, protoFolder)
}
// folder scanning.
files, err := gfile.ScanDirFile(protoFolder, "*.proto", true)
if err != nil {
mlog.Fatal(err)
}
if len(files) == 0 {
mlog.Fatalf(`no proto files found in folder "%s"`, protoFolder)
}
dirSet := gset.NewStrSet()
for _, file := range files {
dirSet.Add(gfile.Dir(file))
}
var (
servicePath = gfile.RealPath(".")
goPathSrc = gfile.RealPath(gfile.Join(genv.Get("GOPATH").String(), "src"))
)
dirSet.Iterator(func(protoDirPath string) bool {
parsingCommand := fmt.Sprintf(
"protoc --gofast_out=plugins=grpc:. %s/*.proto -I%s",
protoDirPath,
servicePath,
)
if goPathSrc != "" {
parsingCommand += " -I" + goPathSrc
}
mlog.Print(parsingCommand)
if output, err := gproc.ShellExec(ctx, parsingCommand); err != nil {
mlog.Print(output)
mlog.Fatal(err)
}
return true
})
// Custom replacement.
//pbFolder := "protobuf"
//_, _ = gfile.ScanDirFileFunc(pbFolder, "*.go", true, func(path string) string {
// content := gfile.GetContents(path)
// content = gstr.ReplaceByArray(content, g.SliceStr{
// `gtime "gtime"`, `gtime "github.com/gogf/gf/v2/os/gtime"`,
// })
// _ = gfile.PutContents(path, content)
// utils.GoFmt(path)
// return path
//})
mlog.Print("done!")
return
}

View File

@ -1,411 +1,7 @@
package cmd
import (
"bytes"
"context"
"fmt"
"strings"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gtag"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
)
import "github.com/gogf/gf/cmd/gf/v2/internal/cmd/genpbentity"
type (
cGenPbEntity struct{}
cGenPbEntityInput struct {
g.Meta `name:"pbentity" config:"{cGenPbEntityConfig}" brief:"{cGenPbEntityBrief}" eg:"{cGenPbEntityEg}" ad:"{cGenPbEntityAd}"`
Path string `name:"path" short:"p" brief:"{cGenPbEntityBriefPath}"`
Package string `name:"package" short:"k" brief:"{cGenPbEntityBriefPackage}"`
Link string `name:"link" short:"l" brief:"{cGenPbEntityBriefLink}"`
Tables string `name:"tables" short:"t" brief:"{cGenPbEntityBriefTables}"`
Prefix string `name:"prefix" short:"f" brief:"{cGenPbEntityBriefPrefix}"`
RemovePrefix string `name:"removePrefix" short:"r" brief:"{cGenPbEntityBriefRemovePrefix}"`
NameCase string `name:"nameCase" short:"n" brief:"{cGenPbEntityBriefNameCase}" d:"Camel"`
JsonCase string `name:"jsonCase" short:"j" brief:"{cGenPbEntityBriefJsonCase}" d:"CamelLower"`
Option string `name:"option" short:"o" brief:"{cGenPbEntityBriefOption}"`
}
cGenPbEntityOutput struct{}
cGenPbEntityInternalInput struct {
cGenPbEntityInput
TableName string // TableName specifies the table name of the table.
NewTableName string // NewTableName specifies the prefix-stripped name of the table.
}
cGenPbEntity = genpbentity.CGenPbEntity
)
const (
cGenPbEntityConfig = `gfcli.gen.pbentity`
cGenPbEntityBrief = `generate entity message files in protobuf3 format`
cGenPbEntityEg = `
gf gen pbentity
gf gen pbentity -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
gf gen pbentity -p ./protocol/demos/entity -t user,user_detail,user_login
gf gen pbentity -r user_
`
cGenPbEntityAd = `
CONFIGURATION SUPPORT
Options are also supported by configuration file.
It's suggested using configuration file instead of command line arguments making producing.
The configuration node name is "gf.gen.pbentity", which also supports multiple databases, for example(config.yaml):
gfcli:
gen:
- pbentity:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
path: "protocol/demos/entity"
tables: "order,products"
package: "demos"
- pbentity:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary"
path: "protocol/demos/entity"
prefix: "primary_"
tables: "user, userDetail"
package: "demos"
option: |
option go_package = "protobuf/demos";
option java_package = "protobuf/demos";
option php_namespace = "protobuf/demos";
`
cGenPbEntityBriefPath = `directory path for generated files`
cGenPbEntityBriefPackage = `package name for all entity proto files`
cGenPbEntityBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
cGenPbEntityBriefTables = `generate models only for given tables, multiple table names separated with ','`
cGenPbEntityBriefPrefix = `add specified prefix for all entity names and entity proto files`
cGenPbEntityBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
cGenPbEntityBriefOption = `extra protobuf options`
cGenPbEntityBriefGroup = `
specifying the configuration group name of database for generated ORM instance,
it's not necessary and the default value is "default"
`
cGenPbEntityBriefNameCase = `
case for message attribute names, default is "Camel":
| Case | Example |
|---------------- |--------------------|
| Camel | AnyKindOfString |
| CamelLower | anyKindOfString | default
| Snake | any_kind_of_string |
| SnakeScreaming | ANY_KIND_OF_STRING |
| SnakeFirstUpper | rgb_code_md5 |
| Kebab | any-kind-of-string |
| KebabScreaming | ANY-KIND-OF-STRING |
`
cGenPbEntityBriefJsonCase = `
case for message json tag, cases are the same as "nameCase", default "CamelLower".
set it to "none" to ignore json tag generating.
`
)
func init() {
gtag.Sets(g.MapStrStr{
`cGenPbEntityConfig`: cGenPbEntityConfig,
`cGenPbEntityBrief`: cGenPbEntityBrief,
`cGenPbEntityEg`: cGenPbEntityEg,
`cGenPbEntityAd`: cGenPbEntityAd,
`cGenPbEntityBriefPath`: cGenPbEntityBriefPath,
`cGenPbEntityBriefPackage`: cGenPbEntityBriefPackage,
`cGenPbEntityBriefLink`: cGenPbEntityBriefLink,
`cGenPbEntityBriefTables`: cGenPbEntityBriefTables,
`cGenPbEntityBriefPrefix`: cGenPbEntityBriefPrefix,
`cGenPbEntityBriefRemovePrefix`: cGenPbEntityBriefRemovePrefix,
`cGenPbEntityBriefGroup`: cGenPbEntityBriefGroup,
`cGenPbEntityBriefNameCase`: cGenPbEntityBriefNameCase,
`cGenPbEntityBriefJsonCase`: cGenPbEntityBriefJsonCase,
`cGenPbEntityBriefOption`: cGenPbEntityBriefOption,
})
}
func (c cGenPbEntity) PbEntity(ctx context.Context, in cGenPbEntityInput) (out *cGenPbEntityOutput, err error) {
var (
config = g.Cfg()
)
if config.Available(ctx) {
v := config.MustGet(ctx, cGenPbEntityConfig)
if v.IsSlice() {
for i := 0; i < len(v.Interfaces()); i++ {
doGenPbEntityForArray(ctx, i, in)
}
} else {
doGenPbEntityForArray(ctx, -1, in)
}
} else {
doGenPbEntityForArray(ctx, -1, in)
}
mlog.Print("done!")
return
}
func doGenPbEntityForArray(ctx context.Context, index int, in cGenPbEntityInput) {
var (
err error
db gdb.DB
)
if index >= 0 {
err = g.Cfg().MustGet(
ctx,
fmt.Sprintf(`%s.%d`, cGenPbEntityConfig, index),
).Scan(&in)
if err != nil {
mlog.Fatalf(`invalid configuration of "%s": %+v`, cGenPbEntityConfig, err)
}
}
if in.Package == "" {
mlog.Fatal("package name should not be empty")
}
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
// It uses user passed database configuration.
if in.Link != "" {
var (
tempGroup = gtime.TimestampNanoStr()
match, _ = gregex.MatchString(`([a-z]+):(.+)`, in.Link)
)
if len(match) == 3 {
gdb.AddConfigNode(tempGroup, gdb.ConfigNode{
Type: gstr.Trim(match[1]),
Link: gstr.Trim(match[2]),
})
db, _ = gdb.Instance(tempGroup)
}
} else {
db = g.DB()
}
if db == nil {
mlog.Fatal("database initialization failed")
}
tableNames := ([]string)(nil)
if in.Tables != "" {
tableNames = gstr.SplitAndTrim(in.Tables, ",")
} else {
tableNames, err = db.Tables(context.TODO())
if err != nil {
mlog.Fatalf("fetching tables failed: \n %v", err)
}
}
for _, tableName := range tableNames {
newTableName := tableName
for _, v := range removePrefixArray {
newTableName = gstr.TrimLeftStr(newTableName, v, 1)
}
generatePbEntityContentFile(ctx, db, cGenPbEntityInternalInput{
cGenPbEntityInput: in,
TableName: tableName,
NewTableName: newTableName,
})
}
}
// generatePbEntityContentFile generates the protobuf files for given table.
func generatePbEntityContentFile(ctx context.Context, db gdb.DB, in cGenPbEntityInternalInput) {
fieldMap, err := db.TableFields(ctx, in.TableName)
if err != nil {
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", in.TableName, err)
}
// Change the `newTableName` if `Prefix` is given.
newTableName := "Entity_" + in.Prefix + in.NewTableName
var (
tableNameCamelCase = gstr.CaseCamel(newTableName)
tableNameSnakeCase = gstr.CaseSnake(newTableName)
entityMessageDefine = generateEntityMessageDefinition(tableNameCamelCase, fieldMap, in)
fileName = gstr.Trim(tableNameSnakeCase, "-_.")
path = gfile.Join(in.Path, fileName+".proto")
)
entityContent := gstr.ReplaceByMap(getTplPbEntityContent(""), g.MapStrStr{
"{PackageName}": in.Package,
"{OptionContent}": in.Option,
"{EntityMessage}": entityMessageDefine,
})
if err := gfile.PutContents(path, strings.TrimSpace(entityContent)); err != nil {
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
} else {
mlog.Print("generated:", path)
}
}
// generateEntityMessageDefinition generates and returns the message definition for specified table.
func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb.TableField, in cGenPbEntityInternalInput) string {
var (
buffer = bytes.NewBuffer(nil)
array = make([][]string, len(fieldMap))
names = sortFieldKeyForPbEntity(fieldMap)
)
for index, name := range names {
array[index] = generateMessageFieldForPbEntity(index+1, fieldMap[name], in)
}
tw := tablewriter.NewWriter(buffer)
tw.SetBorder(false)
tw.SetRowLine(false)
tw.SetAutoWrapText(false)
tw.SetColumnSeparator("")
tw.AppendBulk(array)
tw.Render()
stContent := buffer.String()
// Let's do this hack of table writer for indent!
stContent = gstr.Replace(stContent, " #", "")
buffer.Reset()
buffer.WriteString(fmt.Sprintf("message %s {\n", entityName))
buffer.WriteString(stContent)
buffer.WriteString("}")
return buffer.String()
}
// generateMessageFieldForPbEntity generates and returns the message definition for specified field.
func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in cGenPbEntityInternalInput) []string {
var (
typeName string
comment string
jsonTagStr string
)
t, _ := gregex.ReplaceString(`\(.+\)`, "", field.Type)
t = gstr.Split(gstr.Trim(t), " ")[0]
t = gstr.ToLower(t)
switch t {
case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob":
typeName = "bytes"
case "bit", "int", "tinyint", "small_int", "smallint", "medium_int", "mediumint", "serial":
if gstr.ContainsI(field.Type, "unsigned") {
typeName = "uint32"
} else {
typeName = "int32"
}
case "int8", "big_int", "bigint", "bigserial":
if gstr.ContainsI(field.Type, "unsigned") {
typeName = "uint64"
} else {
typeName = "int64"
}
case "real":
typeName = "float"
case "float", "double", "decimal", "smallmoney":
typeName = "double"
case "bool":
typeName = "bool"
case "datetime", "timestamp", "date", "time":
typeName = "int64"
default:
// Auto detecting type.
switch {
case strings.Contains(t, "int"):
typeName = "int"
case strings.Contains(t, "text") || strings.Contains(t, "char"):
typeName = "string"
case strings.Contains(t, "float") || strings.Contains(t, "double"):
typeName = "double"
case strings.Contains(t, "bool"):
typeName = "bool"
case strings.Contains(t, "binary") || strings.Contains(t, "blob"):
typeName = "bytes"
case strings.Contains(t, "date") || strings.Contains(t, "time"):
typeName = "int64"
default:
typeName = "string"
}
}
comment = gstr.ReplaceByArray(field.Comment, g.SliceStr{
"\n", " ",
"\r", " ",
})
comment = gstr.Trim(comment)
comment = gstr.Replace(comment, `\n`, " ")
comment, _ = gregex.ReplaceString(`\s{2,}`, ` `, comment)
if jsonTagName := formatCase(field.Name, in.JsonCase); jsonTagName != "" {
jsonTagStr = fmt.Sprintf(`[(gogoproto.jsontag) = "%s"]`, jsonTagName)
// beautiful indent.
if index < 10 {
// 3 spaces
jsonTagStr = " " + jsonTagStr
} else if index < 100 {
// 2 spaces
jsonTagStr = " " + jsonTagStr
} else {
// 1 spaces
jsonTagStr = " " + jsonTagStr
}
}
return []string{
" #" + typeName,
" #" + formatCase(field.Name, in.NameCase),
" #= " + gconv.String(index) + jsonTagStr + ";",
" #" + fmt.Sprintf(`// %s`, comment),
}
}
func getTplPbEntityContent(tplEntityPath string) string {
if tplEntityPath != "" {
return gfile.GetContents(tplEntityPath)
}
return consts.TemplatePbEntityMessageContent
}
// formatCase call gstr.Case* function to convert the s to specified case.
func formatCase(str, caseStr string) string {
switch gstr.ToLower(caseStr) {
case gstr.ToLower("Camel"):
return gstr.CaseCamel(str)
case gstr.ToLower("CamelLower"):
return gstr.CaseCamelLower(str)
case gstr.ToLower("Kebab"):
return gstr.CaseKebab(str)
case gstr.ToLower("KebabScreaming"):
return gstr.CaseKebabScreaming(str)
case gstr.ToLower("Snake"):
return gstr.CaseSnake(str)
case gstr.ToLower("SnakeFirstUpper"):
return gstr.CaseSnakeFirstUpper(str)
case gstr.ToLower("SnakeScreaming"):
return gstr.CaseSnakeScreaming(str)
case "none":
return ""
}
return str
}
func sortFieldKeyForPbEntity(fieldMap map[string]*gdb.TableField) []string {
names := make(map[int]string)
for _, field := range fieldMap {
names[field.Index] = field.Name
}
var (
result = make([]string, len(names))
i = 0
j = 0
)
for {
if len(names) == 0 {
break
}
if val, ok := names[i]; ok {
result[j] = val
j++
delete(names, i)
}
i++
}
return result
}

View File

@ -216,6 +216,7 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
}
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
if in.ImportPrefix == "" {
mlog.Debug(`import prefix is empty, trying calculating the import package path using go.mod`)
if !gfile.Exists("go.mod") {
mlog.Fatal("go.mod does not exist in current working directory")
}

View File

@ -9,13 +9,13 @@ import (
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
)
func doClear(ctx context.Context, dirPath string) {
func doClear(ctx context.Context, dirPath string, force bool) {
files, err := gfile.ScanDirFile(dirPath, "*.go", true)
if err != nil {
mlog.Fatal(err)
}
for _, file := range files {
if utils.IsFileDoNotEdit(file) {
if force || utils.IsFileDoNotEdit(file) {
if err = gfile.Remove(file); err != nil {
mlog.Print(err)
}

View File

@ -6,12 +6,13 @@ import (
"fmt"
"strings"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/olekukonko/tablewriter"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
@ -24,7 +25,7 @@ func generateDao(ctx context.Context, in CGenDaoInternalInput) {
dirPathDaoInternal = gfile.Join(dirPathDao, "internal")
)
if in.Clear {
doClear(ctx, dirPathDao)
doClear(ctx, dirPathDao, true)
}
for i := 0; i < len(in.TableNames); i++ {
generateDaoSingle(ctx, generateDaoSingleInput{

View File

@ -18,7 +18,7 @@ import (
func generateDo(ctx context.Context, in CGenDaoInternalInput) {
var dirPathDo = gfile.Join(in.Path, in.DoPath)
if in.Clear {
doClear(ctx, dirPathDo)
doClear(ctx, dirPathDo, false)
}
in.NoJsonTag = true
in.DescriptionTag = false

View File

@ -16,7 +16,7 @@ import (
func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
var dirPathEntity = gfile.Join(in.Path, in.EntityPath)
if in.Clear {
doClear(ctx, dirPathEntity)
doClear(ctx, dirPathEntity, false)
}
// Model content.
for i, tableName := range in.TableNames {

View File

@ -0,0 +1,122 @@
package genpb
import (
"context"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/util/gtag"
)
type (
CGenPb struct{}
CGenPbInput struct {
g.Meta `name:"pb" config:"{CGenPbConfig}" brief:"{CGenPbBrief}" eg:"{CGenPbEg}"`
Path string `name:"path" short:"p" dc:"protobuf file folder path" d:"manifest/protobuf"`
OutputApi string `name:"api" short:"a" dc:"output folder path storing generated go files of api" d:"api"`
OutputCtrl string `name:"ctrl" short:"c" dc:"output folder path storing generated go files of controller" d:"internal/controller"`
}
CGenPbOutput struct{}
)
const (
CGenPbConfig = `gfcli.gen.pb`
CGenPbBrief = `parse proto files and generate protobuf go files`
CGenPbEg = `
gf gen pb
gf gen pb -p . -a . -p .
`
)
func init() {
gtag.Sets(g.MapStrStr{
`CGenPbEg`: CGenPbEg,
`CGenPbBrief`: CGenPbBrief,
`CGenPbConfig`: CGenPbConfig,
})
}
func (c CGenPb) Pb(ctx context.Context, in CGenPbInput) (out *CGenPbOutput, err error) {
// Necessary check.
protoc := gproc.SearchBinary("protoc")
if protoc == "" {
mlog.Fatalf(`command "protoc" not found in your environment, please install protoc first: https://grpc.io/docs/languages/go/quickstart/`)
}
// protocol fold checks.
var (
protoPath = gfile.RealPath(in.Path)
isParsingPWD bool
)
if protoPath == "" {
// Use current working directory as protoPath if there are proto files under.
currentPath := gfile.Pwd()
currentFiles, _ := gfile.ScanDirFile(currentPath, "*.proto")
if len(currentFiles) > 0 {
protoPath = currentPath
isParsingPWD = true
} else {
mlog.Fatalf(`proto files folder "%s" does not exist`, in.Path)
}
}
// output path checks.
outputApiPath := gfile.RealPath(in.OutputApi)
if outputApiPath == "" {
if isParsingPWD {
outputApiPath = protoPath
} else {
mlog.Fatalf(`output api folder "%s" does not exist`, in.OutputApi)
}
}
outputCtrlPath := gfile.RealPath(in.OutputCtrl)
if outputCtrlPath == "" {
if isParsingPWD {
outputCtrlPath = ""
} else {
mlog.Fatalf(`output controller folder "%s" does not exist`, in.OutputCtrl)
}
}
// folder scanning.
files, err := gfile.ScanDirFile(protoPath, "*.proto", true)
if err != nil {
mlog.Fatal(err)
}
if len(files) == 0 {
mlog.Fatalf(`no proto files found in folder "%s"`, in.Path)
}
if err = gfile.Chdir(protoPath); err != nil {
mlog.Fatal(err)
}
for _, file := range files {
var command = gproc.NewProcess(protoc, nil)
command.Args = append(command.Args, "--proto_path="+gfile.Pwd())
command.Args = append(command.Args, "--go_out=paths=source_relative:"+outputApiPath)
command.Args = append(command.Args, "--go-grpc_out=paths=source_relative:"+outputApiPath)
command.Args = append(command.Args, file)
mlog.Print(command.String())
if err = command.Run(ctx); err != nil {
mlog.Fatal(err)
}
}
// Generate struct tag according comment rules.
err = c.generateStructTag(ctx, generateStructTagInput{OutputApiPath: outputApiPath})
if err != nil {
return
}
// Generate controllers according comment rules.
if outputCtrlPath != "" {
err = c.generateController(ctx, generateControllerInput{
OutputApiPath: outputApiPath,
OutputCtrlPath: outputCtrlPath,
})
if err != nil {
return
}
}
mlog.Print("done!")
return
}

View File

@ -0,0 +1,189 @@
package genpb
import (
"context"
"fmt"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
type generateControllerInput struct {
OutputApiPath string
OutputCtrlPath string
}
type generateCtrl struct {
Name string
Package string
Version string
Methods []generateCtrlMethod
}
type generateCtrlMethod struct {
Name string
Definition string
}
const (
controllerTemplate = `
package {Package}
type Controller struct {
{Version}.Unimplemented{Name}Server
}
func Register(s *grpcx.GrpcServer) {
{Version}.Register{Name}Server(s.Server, &Controller{})
}
`
controllerMethodTemplate = `
func (*Controller) {Definition} {
return nil, gerror.NewCode(gcode.CodeNotImplemented)
}
`
)
func (c CGenPb) generateController(ctx context.Context, in generateControllerInput) (err error) {
files, err := gfile.ScanDirFile(in.OutputApiPath, "*_grpc.pb.go", true)
if err != nil {
return err
}
var controllers []generateCtrl
for _, file := range files {
fileControllers, err := c.parseControllers(file)
if err != nil {
return err
}
controllers = append(controllers, fileControllers...)
}
if len(controllers) == 0 {
return nil
}
// Generate controller files.
err = c.doGenerateControllers(in, controllers)
return
}
func (c CGenPb) parseControllers(filePath string) ([]generateCtrl, error) {
var (
controllers []generateCtrl
content = gfile.GetContents(filePath)
)
_, err := gregex.ReplaceStringFuncMatch(
`type (\w+)Server interface {([\s\S]+?)}`,
content,
func(match []string) string {
ctrl := generateCtrl{
Name: match[1],
Package: gfile.Basename(gfile.Dir(gfile.Dir(filePath))),
Version: gfile.Basename(gfile.Dir(filePath)),
Methods: make([]generateCtrlMethod, 0),
}
lines := gstr.Split(match[2], "\n")
for _, line := range lines {
line = gstr.Trim(line)
if line == "" || !gstr.IsLetterUpper(line[0]) {
continue
}
// Comment.
if gregex.IsMatchString(`^//.+`, line) {
continue
}
line, _ = gregex.ReplaceStringFuncMatch(
`^(\w+)\(context\.Context, \*(\w+)\) \(\*(\w+), error\)$`,
line,
func(match []string) string {
return fmt.Sprintf(
`%s(ctx context.Context, req *%s.%s) (res *%s.%s, err error)`,
match[1], ctrl.Version, match[2], ctrl.Version, match[3],
)
},
)
ctrl.Methods = append(ctrl.Methods, generateCtrlMethod{
Name: gstr.Split(line, "(")[0],
Definition: line,
})
}
if len(ctrl.Methods) > 0 {
controllers = append(controllers, ctrl)
}
return match[0]
},
)
return controllers, err
}
func (c CGenPb) doGenerateControllers(in generateControllerInput, controllers []generateCtrl) (err error) {
for _, controller := range controllers {
err = c.doGenerateController(in, controller)
if err != nil {
return err
}
}
err = utils.ReplaceGeneratedContentGFV2(in.OutputCtrlPath)
return nil
}
func (c CGenPb) doGenerateController(in generateControllerInput, controller generateCtrl) (err error) {
var (
folderPath = gfile.Join(in.OutputCtrlPath, controller.Package)
filePath = gfile.Join(folderPath, controller.Package+".go")
isDirty bool
)
if !gfile.Exists(folderPath) {
if err = gfile.Mkdir(folderPath); err != nil {
return err
}
}
if !gfile.Exists(filePath) {
templateContent := gstr.ReplaceByMap(controllerTemplate, g.MapStrStr{
"{Name}": controller.Name,
"{Version}": controller.Version,
"{Package}": controller.Package,
})
if err = gfile.PutContents(filePath, templateContent); err != nil {
return err
}
isDirty = true
}
// Exist controller content.
var ctrlContent string
files, err := gfile.ScanDirFile(folderPath, "*.go", false)
if err != nil {
return err
}
for _, file := range files {
if ctrlContent != "" {
ctrlContent += "\n"
}
ctrlContent += gfile.GetContents(file)
}
// Generate method content.
var generatedContent string
for _, method := range controller.Methods {
if gstr.Contains(ctrlContent, fmt.Sprintf(`%s(`, method.Name)) {
continue
}
if generatedContent != "" {
generatedContent += "\n"
}
generatedContent += gstr.ReplaceByMap(controllerMethodTemplate, g.MapStrStr{
"{Definition}": method.Definition,
})
}
if generatedContent != "" {
err = gfile.PutContentsAppend(filePath, generatedContent)
if err != nil {
return err
}
isDirty = true
}
if isDirty {
utils.GoFmt(filePath)
}
return nil
}

View File

@ -0,0 +1,107 @@
package genpb
import (
"context"
"fmt"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
type generateStructTagInput struct {
OutputApiPath string
}
func (c CGenPb) generateStructTag(ctx context.Context, in generateStructTagInput) (err error) {
files, err := gfile.ScanDirFile(in.OutputApiPath, "*.pb.go", true)
if err != nil {
return err
}
var content string
for _, file := range files {
content = gfile.GetContents(file)
content, err = c.doTagReplacement(ctx, content)
if err != nil {
return err
}
if err = gfile.PutContents(file, content); err != nil {
return err
}
utils.GoFmt(file)
}
return
}
func (c CGenPb) doTagReplacement(ctx context.Context, content string) (string, error) {
content, err := gregex.ReplaceStringFuncMatch(`type (\w+) struct {([\s\S]+?)}`, content, func(match []string) string {
var (
topCommentMatch []string
tailCommentMatch []string
lines = gstr.Split(match[2], "\n")
lineTagMap = gmap.NewListMap()
)
for index, line := range lines {
line = gstr.Trim(line)
if line == "" {
continue
}
// Top comment.
topCommentMatch, _ = gregex.MatchString(`^/[/|\*](.+)`, line)
if len(topCommentMatch) > 1 {
c.tagCommentIntoListMap(gstr.Trim(topCommentMatch[1]), lineTagMap)
continue
}
// Tail comment.
tailCommentMatch, _ = gregex.MatchString(".+?`.+?`.+?//(.+)", line)
if len(tailCommentMatch) > 1 {
c.tagCommentIntoListMap(gstr.Trim(tailCommentMatch[1]), lineTagMap)
}
// Tag injection.
if !lineTagMap.IsEmpty() {
tagContent := c.listMapToStructTag(lineTagMap)
lineTagMap.Clear()
line, _ = gregex.ReplaceString("`(.+)`", fmt.Sprintf("`$1 %s`", tagContent), line)
}
lines[index] = line
}
match[2] = gstr.Join(lines, "\n")
return fmt.Sprintf("type %s struct {%s}", match[1], match[2])
})
return content, err
}
func (c CGenPb) tagCommentIntoListMap(comment string, lineTagMap *gmap.ListMap) {
tagCommentMatch, _ := gregex.MatchString(`^(\w+):(.+)`, comment)
if len(tagCommentMatch) > 1 {
var (
tagName = gstr.Trim(tagCommentMatch[1])
tagContent = gstr.Trim(tagCommentMatch[2])
)
lineTagMap.Set(tagName, lineTagMap.GetVar(tagName).String()+tagContent)
} else {
var (
tagName = "dc"
tagContent = comment
)
lineTagMap.Set(tagName, lineTagMap.GetVar(tagName).String()+tagContent)
}
}
func (c CGenPb) listMapToStructTag(lineTagMap *gmap.ListMap) string {
var tag string
lineTagMap.Iterator(func(key, value interface{}) bool {
if tag != "" {
tag += " "
}
tag += fmt.Sprintf(
`%s:"%s"`,
key, gstr.Replace(gconv.String(value), `"`, `\"`),
)
return true
})
return tag
}

View File

@ -0,0 +1,413 @@
package genpbentity
import (
"bytes"
"context"
"fmt"
"github.com/olekukonko/tablewriter"
"strings"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gtag"
)
type (
CGenPbEntity struct{}
CGenPbEntityInput struct {
g.Meta `name:"pbentity" config:"{CGenPbEntityConfig}" brief:"{CGenPbEntityBrief}" eg:"{CGenPbEntityEg}" ad:"{CGenPbEntityAd}"`
Path string `name:"path" short:"p" brief:"{CGenPbEntityBriefPath}" d:"manifest/protobuf/pbentity"`
Package string `name:"package" short:"k" brief:"{CGenPbEntityBriefPackage}"`
Link string `name:"link" short:"l" brief:"{CGenPbEntityBriefLink}"`
Tables string `name:"tables" short:"t" brief:"{CGenPbEntityBriefTables}"`
Prefix string `name:"prefix" short:"f" brief:"{CGenPbEntityBriefPrefix}"`
RemovePrefix string `name:"removePrefix" short:"r" brief:"{CGenPbEntityBriefRemovePrefix}"`
NameCase string `name:"nameCase" short:"n" brief:"{CGenPbEntityBriefNameCase}" d:"Camel"`
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenPbEntityBriefJsonCase}" d:"CamelLower"`
Option string `name:"option" short:"o" brief:"{CGenPbEntityBriefOption}"`
}
CGenPbEntityOutput struct{}
CGenPbEntityInternalInput struct {
CGenPbEntityInput
DB gdb.DB
TableName string // TableName specifies the table name of the table.
NewTableName string // NewTableName specifies the prefix-stripped name of the table.
}
)
const (
defaultPackageSuffix = `api/pbentity`
CGenPbEntityConfig = `gfcli.gen.pbentity`
CGenPbEntityBrief = `generate entity message files in protobuf3 format`
CGenPbEntityEg = `
gf gen pbentity
gf gen pbentity -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
gf gen pbentity -p ./protocol/demos/entity -t user,user_detail,user_login
gf gen pbentity -r user_ -k github.com/gogf/gf/example/protobuf
gf gen pbentity -r user_
`
CGenPbEntityAd = `
CONFIGURATION SUPPORT
Options are also supported by configuration file.
It's suggested using configuration file instead of command line arguments making producing.
The configuration node name is "gf.gen.pbentity", which also supports multiple databases, for example(config.yaml):
gfcli:
gen:
- pbentity:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
path: "protocol/demos/entity"
tables: "order,products"
package: "demos"
- pbentity:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary"
path: "protocol/demos/entity"
prefix: "primary_"
tables: "user, userDetail"
package: "demos"
option: |
option go_package = "protobuf/demos";
option java_package = "protobuf/demos";
option php_namespace = "protobuf/demos";
`
CGenPbEntityBriefPath = `directory path for generated files storing`
CGenPbEntityBriefPackage = `package path for all entity proto files`
CGenPbEntityBriefLink = `database configuration, the same as the ORM configuration of GoFrame`
CGenPbEntityBriefTables = `generate models only for given tables, multiple table names separated with ','`
CGenPbEntityBriefPrefix = `add specified prefix for all entity names and entity proto files`
CGenPbEntityBriefRemovePrefix = `remove specified prefix of the table, multiple prefix separated with ','`
CGenPbEntityBriefOption = `extra protobuf options`
CGenPbEntityBriefGroup = `
specifying the configuration group name of database for generated ORM instance,
it's not necessary and the default value is "default"
`
CGenPbEntityBriefNameCase = `
case for message attribute names, default is "Camel":
| Case | Example |
|---------------- |--------------------|
| Camel | AnyKindOfString |
| CamelLower | anyKindOfString | default
| Snake | any_kind_of_string |
| SnakeScreaming | ANY_KIND_OF_STRING |
| SnakeFirstUpper | rgb_code_md5 |
| Kebab | any-kind-of-string |
| KebabScreaming | ANY-KIND-OF-STRING |
`
CGenPbEntityBriefJsonCase = `
case for message json tag, cases are the same as "nameCase", default "CamelLower".
set it to "none" to ignore json tag generating.
`
)
func init() {
gtag.Sets(g.MapStrStr{
`CGenPbEntityConfig`: CGenPbEntityConfig,
`CGenPbEntityBrief`: CGenPbEntityBrief,
`CGenPbEntityEg`: CGenPbEntityEg,
`CGenPbEntityAd`: CGenPbEntityAd,
`CGenPbEntityBriefPath`: CGenPbEntityBriefPath,
`CGenPbEntityBriefPackage`: CGenPbEntityBriefPackage,
`CGenPbEntityBriefLink`: CGenPbEntityBriefLink,
`CGenPbEntityBriefTables`: CGenPbEntityBriefTables,
`CGenPbEntityBriefPrefix`: CGenPbEntityBriefPrefix,
`CGenPbEntityBriefRemovePrefix`: CGenPbEntityBriefRemovePrefix,
`CGenPbEntityBriefGroup`: CGenPbEntityBriefGroup,
`CGenPbEntityBriefNameCase`: CGenPbEntityBriefNameCase,
`CGenPbEntityBriefJsonCase`: CGenPbEntityBriefJsonCase,
`CGenPbEntityBriefOption`: CGenPbEntityBriefOption,
})
}
func (c CGenPbEntity) PbEntity(ctx context.Context, in CGenPbEntityInput) (out *CGenPbEntityOutput, err error) {
var (
config = g.Cfg()
)
if config.Available(ctx) {
v := config.MustGet(ctx, CGenPbEntityConfig)
if v.IsSlice() {
for i := 0; i < len(v.Interfaces()); i++ {
doGenPbEntityForArray(ctx, i, in)
}
} else {
doGenPbEntityForArray(ctx, -1, in)
}
} else {
doGenPbEntityForArray(ctx, -1, in)
}
mlog.Print("done!")
return
}
func doGenPbEntityForArray(ctx context.Context, index int, in CGenPbEntityInput) {
var (
err error
db gdb.DB
)
if index >= 0 {
err = g.Cfg().MustGet(
ctx,
fmt.Sprintf(`%s.%d`, CGenPbEntityConfig, index),
).Scan(&in)
if err != nil {
mlog.Fatalf(`invalid configuration of "%s": %+v`, CGenPbEntityConfig, err)
}
}
if in.Package == "" {
mlog.Debug(`package parameter is empty, trying calculating the package path using go.mod`)
if !gfile.Exists("go.mod") {
mlog.Fatal("go.mod does not exist in current working directory")
}
var (
modName string
goModContent = gfile.GetContents("go.mod")
match, _ = gregex.MatchString(`^module\s+(.+)\s*`, goModContent)
)
if len(match) > 1 {
modName = gstr.Trim(match[1])
in.Package = modName + "/" + defaultPackageSuffix
} else {
mlog.Fatal("module name does not found in go.mod")
}
}
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
// It uses user passed database configuration.
if in.Link != "" {
var (
tempGroup = gtime.TimestampNanoStr()
match, _ = gregex.MatchString(`([a-z]+):(.+)`, in.Link)
)
if len(match) == 3 {
gdb.AddConfigNode(tempGroup, gdb.ConfigNode{
Type: gstr.Trim(match[1]),
Link: gstr.Trim(match[2]),
})
db, _ = gdb.Instance(tempGroup)
}
} else {
db = g.DB()
}
if db == nil {
mlog.Fatal("database initialization failed")
}
tableNames := ([]string)(nil)
if in.Tables != "" {
tableNames = gstr.SplitAndTrim(in.Tables, ",")
} else {
tableNames, err = db.Tables(context.TODO())
if err != nil {
mlog.Fatalf("fetching tables failed: \n %v", err)
}
}
for _, tableName := range tableNames {
newTableName := tableName
for _, v := range removePrefixArray {
newTableName = gstr.TrimLeftStr(newTableName, v, 1)
}
generatePbEntityContentFile(ctx, CGenPbEntityInternalInput{
CGenPbEntityInput: in,
DB: db,
TableName: tableName,
NewTableName: newTableName,
})
}
}
// generatePbEntityContentFile generates the protobuf files for given table.
func generatePbEntityContentFile(ctx context.Context, in CGenPbEntityInternalInput) {
fieldMap, err := in.DB.TableFields(ctx, in.TableName)
if err != nil {
mlog.Fatalf("fetching tables fields failed for table '%s':\n%v", in.TableName, err)
}
// Change the `newTableName` if `Prefix` is given.
newTableName := in.Prefix + in.NewTableName
var (
imports string
tableNameCamelCase = gstr.CaseCamel(newTableName)
tableNameSnakeCase = gstr.CaseSnake(newTableName)
entityMessageDefine = generateEntityMessageDefinition(tableNameCamelCase, fieldMap, in)
fileName = gstr.Trim(tableNameSnakeCase, "-_.")
path = gfile.Join(in.Path, fileName+".proto")
)
if gstr.Contains(entityMessageDefine, "google.protobuf.Timestamp") {
imports = `import "google/protobuf/timestamp.proto";`
}
entityContent := gstr.ReplaceByMap(getTplPbEntityContent(""), g.MapStrStr{
"{Imports}": imports,
"{PackageName}": gfile.Basename(in.Package),
"{GoPackage}": in.Package,
"{OptionContent}": in.Option,
"{EntityMessage}": entityMessageDefine,
})
if err := gfile.PutContents(path, strings.TrimSpace(entityContent)); err != nil {
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
} else {
mlog.Print("generated:", path)
}
}
// generateEntityMessageDefinition generates and returns the message definition for specified table.
func generateEntityMessageDefinition(entityName string, fieldMap map[string]*gdb.TableField, in CGenPbEntityInternalInput) string {
var (
buffer = bytes.NewBuffer(nil)
array = make([][]string, len(fieldMap))
names = sortFieldKeyForPbEntity(fieldMap)
)
for index, name := range names {
array[index] = generateMessageFieldForPbEntity(index+1, fieldMap[name], in)
}
tw := tablewriter.NewWriter(buffer)
tw.SetBorder(false)
tw.SetRowLine(false)
tw.SetAutoWrapText(false)
tw.SetColumnSeparator("")
tw.AppendBulk(array)
tw.Render()
stContent := buffer.String()
// Let's do this hack of table writer for indent!
stContent = gstr.Replace(stContent, " #", "")
buffer.Reset()
buffer.WriteString(fmt.Sprintf("message %s {\n", entityName))
buffer.WriteString(stContent)
buffer.WriteString("}")
return buffer.String()
}
// generateMessageFieldForPbEntity generates and returns the message definition for specified field.
func generateMessageFieldForPbEntity(index int, field *gdb.TableField, in CGenPbEntityInternalInput) []string {
var (
typeName string
comment string
jsonTagStr string
err error
ctx = gctx.GetInitCtx()
)
typeName, err = in.DB.CheckLocalTypeForField(ctx, field.Type, nil)
if err != nil {
panic(err)
}
var typeMapping = map[string]string{
gdb.LocalTypeString: "string",
gdb.LocalTypeDate: "google.protobuf.Timestamp",
gdb.LocalTypeDatetime: "google.protobuf.Timestamp",
gdb.LocalTypeInt: "int32",
gdb.LocalTypeUint: "uint32",
gdb.LocalTypeInt64: "int64",
gdb.LocalTypeUint64: "uint64",
gdb.LocalTypeIntSlice: "repeated int32",
gdb.LocalTypeInt64Slice: "repeated int64",
gdb.LocalTypeUint64Slice: "repeated uint64",
gdb.LocalTypeInt64Bytes: "repeated int64",
gdb.LocalTypeUint64Bytes: "repeated uint64",
gdb.LocalTypeFloat32: "float32",
gdb.LocalTypeFloat64: "float64",
gdb.LocalTypeBytes: "[]byte",
gdb.LocalTypeBool: "bool",
gdb.LocalTypeJson: "string",
gdb.LocalTypeJsonb: "string",
}
typeName = typeMapping[typeName]
if typeName == "" {
typeName = "string"
}
comment = gstr.ReplaceByArray(field.Comment, g.SliceStr{
"\n", " ",
"\r", " ",
})
comment = gstr.Trim(comment)
comment = gstr.Replace(comment, `\n`, " ")
comment, _ = gregex.ReplaceString(`\s{2,}`, ` `, comment)
if jsonTagName := formatCase(field.Name, in.JsonCase); jsonTagName != "" {
// beautiful indent.
if index < 10 {
// 3 spaces
jsonTagStr = " " + jsonTagStr
} else if index < 100 {
// 2 spaces
jsonTagStr = " " + jsonTagStr
} else {
// 1 spaces
jsonTagStr = " " + jsonTagStr
}
}
return []string{
" #" + typeName,
" #" + formatCase(field.Name, in.NameCase),
" #= " + gconv.String(index) + jsonTagStr + ";",
" #" + fmt.Sprintf(`// %s`, comment),
}
}
func getTplPbEntityContent(tplEntityPath string) string {
if tplEntityPath != "" {
return gfile.GetContents(tplEntityPath)
}
return consts.TemplatePbEntityMessageContent
}
// formatCase call gstr.Case* function to convert the s to specified case.
func formatCase(str, caseStr string) string {
switch gstr.ToLower(caseStr) {
case gstr.ToLower("Camel"):
return gstr.CaseCamel(str)
case gstr.ToLower("CamelLower"):
return gstr.CaseCamelLower(str)
case gstr.ToLower("Kebab"):
return gstr.CaseKebab(str)
case gstr.ToLower("KebabScreaming"):
return gstr.CaseKebabScreaming(str)
case gstr.ToLower("Snake"):
return gstr.CaseSnake(str)
case gstr.ToLower("SnakeFirstUpper"):
return gstr.CaseSnakeFirstUpper(str)
case gstr.ToLower("SnakeScreaming"):
return gstr.CaseSnakeScreaming(str)
case "none":
return ""
}
return str
}
func sortFieldKeyForPbEntity(fieldMap map[string]*gdb.TableField) []string {
names := make(map[int]string)
for _, field := range fieldMap {
names[field.Index] = field.Name
}
var (
result = make([]string, len(names))
i = 0
j = 0
)
for {
if len(names) == 0 {
break
}
if val, ok := names[i]; ok {
result[j] = val
j++
delete(names, i)
}
i++
}
return result
}

View File

@ -189,6 +189,10 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
generatedDstFilePathSet.Add(dstFilePath)
for _, file := range files {
fileContent = gfile.GetContents(file)
fileContent, err := gregex.ReplaceString(`/[/|\*](.+)`, "", fileContent)
if err != nil {
return nil, err
}
// Calculate imported packages of source go files.
err = c.calculateImportedPackages(fileContent, srcImportedPackages)
if err != nil {
@ -254,7 +258,7 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
}
// Replace v1 to v2 for GoFrame.
if err = c.replaceGeneratedServiceContentGFV2(in); err != nil {
if err = utils.ReplaceGeneratedContentGFV2(in.DstFolder); err != nil {
return nil, err
}
mlog.Printf(`gofmt go files in "%s"`, in.DstFolder)
@ -264,14 +268,3 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
mlog.Print(`done!`)
return
}
func (c CGenService) replaceGeneratedServiceContentGFV2(in CGenServiceInput) (err error) {
return gfile.ReplaceDirFunc(func(path, content string) string {
if gstr.Contains(content, `"github.com/gogf/gf`) && !gstr.Contains(content, `"github.com/gogf/gf/v2`) {
content = gstr.Replace(content, `"github.com/gogf/gf"`, `"github.com/gogf/gf/v2"`)
content = gstr.Replace(content, `"github.com/gogf/gf/`, `"github.com/gogf/gf/v2/`)
return content
}
return content
}, in.DstFolder, "*.go", false)
}

View File

@ -56,7 +56,7 @@ func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool,
generatingInterfaceCheck string
)
// Variable definitions.
for structName, _ := range in.SrcStructFunctions {
for structName := range in.SrcStructFunctions {
generatingInterfaceCheck = fmt.Sprintf(`[^\w\d]+%s.I%s[^\w\d]`, in.DstPackageName, structName)
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
continue
@ -75,7 +75,7 @@ func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool,
generatedContent += "\n"
}
// Variable register function definitions.
for structName, _ := range in.SrcStructFunctions {
for structName := range in.SrcStructFunctions {
generatingInterfaceCheck = fmt.Sprintf(`[^\w\d]+%s.I%s[^\w\d]`, in.DstPackageName, structName)
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
continue

View File

@ -9,9 +9,9 @@ syntax = "proto3";
package {PackageName};
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option go_package = "{GoPackage}";
{OptionContent}
{Imports}
{EntityMessage}
`

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -29,6 +29,7 @@ type serviceInstallAvailablePath struct {
filePath string
writable bool
installed bool
IsSelf bool
}
func (s serviceInstall) Run(ctx context.Context) (err error) {
@ -131,14 +132,14 @@ func (s serviceInstall) Run(ctx context.Context) (err error) {
}
// IsInstalled checks and returns whether the binary is installed.
func (s serviceInstall) IsInstalled() bool {
func (s serviceInstall) IsInstalled() (*serviceInstallAvailablePath, bool) {
paths := s.getAvailablePaths()
for _, aPath := range paths {
if aPath.installed {
return true
return &aPath, true
}
}
return false
return nil, false
}
// getGoPathBinFilePath retrieves ad returns the GOPATH/bin path for binary.
@ -214,6 +215,7 @@ func (s serviceInstall) checkAndAppendToAvailablePath(folderPaths []serviceInsta
filePath = gfile.Join(dirPath, binaryFileName)
writable = gfile.IsWritable(dirPath)
installed = gfile.Exists(filePath)
self = gfile.SelfPath() == filePath
)
if !writable && !installed {
return folderPaths
@ -225,5 +227,6 @@ func (s serviceInstall) checkAndAppendToAvailablePath(folderPaths []serviceInsta
writable: writable,
filePath: filePath,
installed: installed,
IsSelf: self,
})
}

View File

@ -3,12 +3,13 @@ package utils
import (
"context"
"fmt"
"golang.org/x/tools/imports"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/text/gstr"
"golang.org/x/tools/imports"
)
// GoFmt formats the source file and adds or removes import statements as necessary.
@ -52,3 +53,16 @@ func IsFileDoNotEdit(filePath string) bool {
}
return gstr.Contains(gfile.GetContents(filePath), consts.DoNotEditKey)
}
// ReplaceGeneratedContentGFV2 replaces generated go content from goframe v1 to v2.
func ReplaceGeneratedContentGFV2(folderPath string) (err error) {
return gfile.ReplaceDirFunc(func(path, content string) string {
if gstr.Contains(content, `"github.com/gogf/gf`) && !gstr.Contains(content, `"github.com/gogf/gf/v2`) {
content = gstr.Replace(content, `"github.com/gogf/gf"`, `"github.com/gogf/gf/v2"`)
content = gstr.Replace(content, `"github.com/gogf/gf/`, `"github.com/gogf/gf/v2/`)
content = gstr.Replace(content, `"github.com/gogf/gf/v2/contrib/`, `"github.com/gogf/gf/contrib/`)
return content
}
return content
}, folderPath, "*.go", true)
}

View File

@ -207,9 +207,11 @@ func (a *SortedArray) doRemoveWithoutLock(index int) (value interface{}, found b
// RemoveValue removes an item by value.
// It returns true if value is found in the array, or else false if not found.
func (a *SortedArray) RemoveValue(value interface{}) bool {
if i := a.Search(value); i != -1 {
a.Remove(i)
return true
a.mu.Lock()
defer a.mu.Unlock()
if i, r := a.binSearch(value, false); r == 0 {
_, res := a.doRemoveWithoutLock(i)
return res
}
return false
}

View File

@ -193,9 +193,11 @@ func (a *SortedIntArray) doRemoveWithoutLock(index int) (value int, found bool)
// RemoveValue removes an item by value.
// It returns true if value is found in the array, or else false if not found.
func (a *SortedIntArray) RemoveValue(value int) bool {
if i := a.Search(value); i != -1 {
_, found := a.Remove(i)
return found
a.mu.Lock()
defer a.mu.Unlock()
if i, r := a.binSearch(value, false); r == 0 {
_, res := a.doRemoveWithoutLock(i)
return res
}
return false
}

View File

@ -179,9 +179,11 @@ func (a *SortedStrArray) doRemoveWithoutLock(index int) (value string, found boo
// RemoveValue removes an item by value.
// It returns true if value is found in the array, or else false if not found.
func (a *SortedStrArray) RemoveValue(value string) bool {
if i := a.Search(value); i != -1 {
a.Remove(i)
return true
a.mu.Lock()
defer a.mu.Unlock()
if i, r := a.binSearch(value, false); r == 0 {
_, res := a.doRemoveWithoutLock(i)
return res
}
return false
}

View File

@ -514,3 +514,24 @@ func (m *AnyAnyMap) DeepCopy() interface{} {
}
return NewFrom(data, m.mu.IsSafe())
}
// IsSubOf checks whether the current map is a sub-map of `other`.
func (m *AnyAnyMap) IsSubOf(other *AnyAnyMap) bool {
if m == other {
return true
}
m.mu.RLock()
defer m.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key, value := range m.data {
otherValue, ok := other.data[key]
if !ok {
return false
}
if otherValue != value {
return false
}
}
return true
}

View File

@ -16,6 +16,7 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)
// IntAnyMap implements map[int]interface{} with RWMutex that has switch.
type IntAnyMap struct {
mu rwmutex.RWMutex
data map[int]interface{}
@ -514,3 +515,24 @@ func (m *IntAnyMap) DeepCopy() interface{} {
}
return NewIntAnyMapFrom(data, m.mu.IsSafe())
}
// IsSubOf checks whether the current map is a sub-map of `other`.
func (m *IntAnyMap) IsSubOf(other *IntAnyMap) bool {
if m == other {
return true
}
m.mu.RLock()
defer m.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key, value := range m.data {
otherValue, ok := other.data[key]
if !ok {
return false
}
if otherValue != value {
return false
}
}
return true
}

View File

@ -13,6 +13,7 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)
// IntIntMap implements map[int]int with RWMutex that has switch.
type IntIntMap struct {
mu rwmutex.RWMutex
data map[int]int
@ -484,3 +485,24 @@ func (m *IntIntMap) DeepCopy() interface{} {
}
return NewIntIntMapFrom(data, m.mu.IsSafe())
}
// IsSubOf checks whether the current map is a sub-map of `other`.
func (m *IntIntMap) IsSubOf(other *IntIntMap) bool {
if m == other {
return true
}
m.mu.RLock()
defer m.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key, value := range m.data {
otherValue, ok := other.data[key]
if !ok {
return false
}
if otherValue != value {
return false
}
}
return true
}

View File

@ -13,6 +13,7 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)
// IntStrMap implements map[int]string with RWMutex that has switch.
type IntStrMap struct {
mu rwmutex.RWMutex
data map[int]string
@ -484,3 +485,24 @@ func (m *IntStrMap) DeepCopy() interface{} {
}
return NewIntStrMapFrom(data, m.mu.IsSafe())
}
// IsSubOf checks whether the current map is a sub-map of `other`.
func (m *IntStrMap) IsSubOf(other *IntStrMap) bool {
if m == other {
return true
}
m.mu.RLock()
defer m.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key, value := range m.data {
otherValue, ok := other.data[key]
if !ok {
return false
}
if otherValue != value {
return false
}
}
return true
}

View File

@ -501,3 +501,24 @@ func (m *StrAnyMap) DeepCopy() interface{} {
}
return NewStrAnyMapFrom(data, m.mu.IsSafe())
}
// IsSubOf checks whether the current map is a sub-map of `other`.
func (m *StrAnyMap) IsSubOf(other *StrAnyMap) bool {
if m == other {
return true
}
m.mu.RLock()
defer m.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key, value := range m.data {
otherValue, ok := other.data[key]
if !ok {
return false
}
if otherValue != value {
return false
}
}
return true
}

View File

@ -14,6 +14,7 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)
// StrIntMap implements map[string]int with RWMutex that has switch.
type StrIntMap struct {
mu rwmutex.RWMutex
data map[string]int
@ -488,3 +489,24 @@ func (m *StrIntMap) DeepCopy() interface{} {
}
return NewStrIntMapFrom(data, m.mu.IsSafe())
}
// IsSubOf checks whether the current map is a sub-map of `other`.
func (m *StrIntMap) IsSubOf(other *StrIntMap) bool {
if m == other {
return true
}
m.mu.RLock()
defer m.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key, value := range m.data {
otherValue, ok := other.data[key]
if !ok {
return false
}
if otherValue != value {
return false
}
}
return true
}

View File

@ -14,6 +14,7 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)
// StrStrMap implements map[string]string with RWMutex that has switch.
type StrStrMap struct {
mu rwmutex.RWMutex
data map[string]string
@ -477,3 +478,24 @@ func (m *StrStrMap) DeepCopy() interface{} {
}
return NewStrStrMapFrom(data, m.mu.IsSafe())
}
// IsSubOf checks whether the current map is a sub-map of `other`.
func (m *StrStrMap) IsSubOf(other *StrStrMap) bool {
if m == other {
return true
}
m.mu.RLock()
defer m.mu.RUnlock()
other.mu.RLock()
defer other.mu.RUnlock()
for key, value := range m.data {
otherValue, ok := other.data[key]
if !ok {
return false
}
if otherValue != value {
return false
}
}
return true
}

View File

@ -391,3 +391,18 @@ func Test_AnyAnyMap_DeepCopy(t *testing.T) {
t.AssertNE(m.Get("k1"), n.Get("k1"))
})
}
func Test_AnyAnyMap_IsSubOf(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m1 := gmap.NewAnyAnyMapFrom(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
})
m2 := gmap.NewAnyAnyMapFrom(g.MapAnyAny{
"k2": "v2",
})
t.Assert(m1.IsSubOf(m2), false)
t.Assert(m2.IsSubOf(m1), true)
t.Assert(m2.IsSubOf(m2), true)
})
}

View File

@ -375,3 +375,18 @@ func Test_IntAnyMap_DeepCopy(t *testing.T) {
t.AssertNE(m.Get(1), n.Get(1))
})
}
func Test_IntAnyMap_IsSubOf(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m1 := gmap.NewIntAnyMapFrom(g.MapIntAny{
1: "v1",
2: "v2",
})
m2 := gmap.NewIntAnyMapFrom(g.MapIntAny{
2: "v2",
})
t.Assert(m1.IsSubOf(m2), false)
t.Assert(m2.IsSubOf(m1), true)
t.Assert(m2.IsSubOf(m2), true)
})
}

View File

@ -383,3 +383,18 @@ func Test_IntIntMap_DeepCopy(t *testing.T) {
t.AssertNE(m.Get(1), n.Get(1))
})
}
func Test_IntIntMap_IsSubOf(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m1 := gmap.NewIntAnyMapFrom(g.MapIntAny{
1: 1,
2: 2,
})
m2 := gmap.NewIntAnyMapFrom(g.MapIntAny{
2: 2,
})
t.Assert(m1.IsSubOf(m2), false)
t.Assert(m2.IsSubOf(m1), true)
t.Assert(m2.IsSubOf(m2), true)
})
}

View File

@ -447,3 +447,18 @@ func Test_IntStrMap_DeepCopy(t *testing.T) {
t.AssertNE(m.Get(1), n.Get(1))
})
}
func Test_IntStrMap_IsSubOf(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m1 := gmap.NewIntStrMapFrom(g.MapIntStr{
1: "v1",
2: "v2",
})
m2 := gmap.NewIntStrMapFrom(g.MapIntStr{
2: "v2",
})
t.Assert(m1.IsSubOf(m2), false)
t.Assert(m2.IsSubOf(m1), true)
t.Assert(m2.IsSubOf(m2), true)
})
}

View File

@ -381,3 +381,18 @@ func Test_StrAnyMap_DeepCopy(t *testing.T) {
t.AssertNE(m.Get("key1"), n.Get("key1"))
})
}
func Test_StrAnyMap_IsSubOf(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m1 := gmap.NewStrAnyMapFrom(g.MapStrAny{
"k1": "v1",
"k2": "v2",
})
m2 := gmap.NewStrAnyMapFrom(g.MapStrAny{
"k2": "v2",
})
t.Assert(m1.IsSubOf(m2), false)
t.Assert(m2.IsSubOf(m1), true)
t.Assert(m2.IsSubOf(m2), true)
})
}

View File

@ -389,3 +389,18 @@ func Test_StrIntMap_DeepCopy(t *testing.T) {
t.AssertNE(m.Get("key1"), n.Get("key1"))
})
}
func Test_StrIntMap_IsSubOf(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m1 := gmap.NewStrIntMapFrom(g.MapStrInt{
"k1": 1,
"k2": 2,
})
m2 := gmap.NewStrIntMapFrom(g.MapStrInt{
"k2": 2,
})
t.Assert(m1.IsSubOf(m2), false)
t.Assert(m2.IsSubOf(m1), true)
t.Assert(m2.IsSubOf(m2), true)
})
}

View File

@ -388,3 +388,18 @@ func Test_StrStrMap_DeepCopy(t *testing.T) {
t.AssertNE(m.Get("key1"), n.Get("key1"))
})
}
func Test_StrStrMap_IsSubOf(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m1 := gmap.NewStrStrMapFrom(g.MapStrStr{
"k1": "v1",
"k2": "v2",
})
m2 := gmap.NewStrStrMapFrom(g.MapStrStr{
"k2": "v2",
})
t.Assert(m1.IsSubOf(m2), false)
t.Assert(m2.IsSubOf(m1), true)
t.Assert(m2.IsSubOf(m2), true)
})
}

View File

@ -114,8 +114,8 @@ func TestSet_LockFunc(t *testing.T) {
t.Assert(s.Size(), 2)
s.RLockFunc(func(m map[interface{}]struct{}) {
t.Assert(m, map[interface{}]struct{}{
3: struct{}{},
2: struct{}{},
3: {},
2: {},
})
})
})

View File

@ -94,8 +94,8 @@ func TestIntSet_LockFunc(t *testing.T) {
t.Assert(s.Size(), 2)
s.RLockFunc(func(m map[int]struct{}) {
t.Assert(m, map[int]struct{}{
3: struct{}{},
2: struct{}{},
3: {},
2: {},
})
})
})

View File

@ -105,8 +105,8 @@ func TestStrSet_LockFunc(t *testing.T) {
t.Assert(s.Size(), 2)
s.RLockFunc(func(m map[string]struct{}) {
t.Assert(m, map[string]struct{}{
"3": struct{}{},
"2": struct{}{},
"3": {},
"2": {},
})
})
})

View File

@ -51,7 +51,7 @@ func init() {
}
```
## Import boot package in top of main
## Import boot package on top of main
It is strongly recommended import your boot package in top of your `main.go`.

View File

@ -4,7 +4,7 @@ go 1.15
require (
github.com/gogf/gf/v2 v2.0.0
github.com/polarismesh/polaris-go v1.2.0-beta.3
github.com/polarismesh/polaris-go v1.3.0
)
replace github.com/gogf/gf/v2 => ../../../

View File

@ -227,8 +227,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polarismesh/polaris-go v1.2.0-beta.3 h1:sqN50VGign37xVFp9nrN1RoLXacsB0QfRYUtuCWMJGI=
github.com/polarismesh/polaris-go v1.2.0-beta.3/go.mod h1:HsN0ierETIujHpmnnYJ3qkwQw4QGAECuHvBZTDaw1tI=
github.com/polarismesh/polaris-go v1.3.0 h1:KZKX//ow4OPPoS5+s7h07ptprg+2AcNVGrN6WakC9QM=
github.com/polarismesh/polaris-go v1.3.0/go.mod h1:HsN0ierETIujHpmnnYJ3qkwQw4QGAECuHvBZTDaw1tI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=

View File

@ -10,21 +10,17 @@ package polaris
import (
"context"
"github.com/polarismesh/polaris-go"
"github.com/polarismesh/polaris-go/api"
"github.com/polarismesh/polaris-go/pkg/model"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/text/gstr"
"github.com/polarismesh/polaris-go"
"github.com/polarismesh/polaris-go/api"
"github.com/polarismesh/polaris-go/pkg/model"
)
// LogDir sets the log directory for polaris.
func LogDir(dir string) error {
return api.SetLoggersDir(dir)
}
// Config is the configuration for polaris.
type Config struct {
// The namespace of the configuration.
@ -65,14 +61,14 @@ func New(ctx context.Context, config Config) (adapter gcfg.Adapter, err error) {
)
if configAPI, err = polaris.NewConfigAPIByFile(config.Path); err != nil {
err = gerror.Wrapf(err, "Polaris configuration initialization failed with config: %+v", config)
err = gerror.Wrapf(err, "Polaris configuration initialization failed with config: %+v", config)
return
}
// set log dir
if gstr.Trim(config.LogDir) == "" {
config.LogDir = defaultLogDir
}
if err = LogDir(config.LogDir); err != nil {
if err = client.LogDir(config.LogDir); err != nil {
err = gerror.Wrap(err, "set polaris log dir failed")
return
}
@ -85,6 +81,11 @@ func New(ctx context.Context, config Config) (adapter gcfg.Adapter, err error) {
return client, nil
}
// LogDir sets the log directory for polaris.
func (c *Client) LogDir(dir string) error {
return api.SetLoggersDir(dir)
}
// Available checks and returns the backend configuration service is available.
// The optional parameter `resource` specifies certain configuration resource.
//
@ -103,7 +104,7 @@ func (c *Client) Available(ctx context.Context, resource ...string) (ok bool) {
return c.client.GetNamespace() == namespace
}
// Get retrieves and returns value by specified `pattern` in current resource.
// Get retrieves and return value by specified `pattern` in current resource.
// Pattern like:
// "x.y.z" for map item.
// "x.0.y" for slice item.
@ -117,7 +118,7 @@ func (c *Client) Get(ctx context.Context, pattern string) (value interface{}, er
}
// Data retrieves and returns all configuration data in current resource as map.
// Note that this function may lead lots of memory usage if configuration data is too large,
// Note that this function may lead to lots of memory usage if configuration data are too large,
// you can implement this function if necessary.
func (c *Client) Data(ctx context.Context) (data map[string]interface{}, err error) {
if c.value.IsNil() {

View File

@ -58,6 +58,7 @@ Note:
- It does not support `Save/Replace` features.
- It does not support `LastInsertId`.
- It supports server version >= `SQL Server2005`
- It ONLY supports datetime2 and datetimeoffset types for auto handling created_at/updated_at/deleted_at columns, because datetime type does not support microseconds precision when column value is passed as string.
## Oracle
```

View File

@ -455,9 +455,9 @@ func TestDriverClickhouse_NilTime(t *testing.T) {
Col9: uuid.New(),
Col7: []interface{}{ // Tuple(String, UInt8, Array(Map(String, String)))
"String Value", uint8(5), []map[string]string{
map[string]string{"key": "value"},
map[string]string{"key": "value"},
map[string]string{"key": "value"},
{"key": "value"},
{"key": "value"},
{"key": "value"},
}},
Col11: money,
Col12: &strMoney,
@ -494,9 +494,9 @@ func TestDriverClickhouse_BatchInsert(t *testing.T) {
"Col6": []string{"Q", "W", "E", "R", "T", "Y"}, // Array(String)
"Col7": []interface{}{ // Tuple(String, UInt8, Array(Map(String, String)))
"String Value", uint8(5), []map[string]string{
map[string]string{"key": "value"},
map[string]string{"key": "value"},
map[string]string{"key": "value"},
{"key": "value"},
{"key": "value"},
{"key": "value"},
},
},
"Col8": gtime.Now(),

View File

@ -23,6 +23,7 @@ import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gutil"
)
@ -283,23 +284,7 @@ func parseValue(listOne gdb.Map, char struct {
)
}
va := reflect.ValueOf(listOne[column])
ty := reflect.TypeOf(listOne[column])
saveValue := ""
switch ty.Kind() {
case reflect.String:
saveValue = va.String()
case reflect.Int:
saveValue = strconv.FormatInt(va.Int(), 10)
case reflect.Int64:
saveValue = strconv.FormatInt(va.Int(), 10)
default:
// The fish has no chance getting here.
// Nothing to do.
}
saveValue := gconv.String(listOne[column])
queryValues = append(
queryValues,
fmt.Sprintf(

View File

@ -125,9 +125,9 @@ func createTable(table ...string) (name string) {
"ENABLED" INT DEFAULT 1 NOT NULL,
"DELETED" INT DEFAULT 0 NOT NULL,
"CREATED_BY" VARCHAR(32) DEFAULT '' NOT NULL,
"CREATED_TIME" TIMESTAMP(0) DEFAULT CURRENT_TIMESTAMP() NOT NULL,
"CREATED_TIME" TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP() NOT NULL,
"UPDATED_BY" VARCHAR(32) DEFAULT '' NOT NULL,
"UPDATED_TIME" TIMESTAMP(0) DEFAULT CURRENT_TIMESTAMP() NOT NULL,
"UPDATED_TIME" TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP() NOT NULL,
NOT CLUSTER PRIMARY KEY("ID")) STORAGE(ON "MAIN", CLUSTERBTR) ;
`, name)); err != nil {
gtest.Fatal(err)

View File

@ -233,7 +233,7 @@ func Test_DB_Insert(t *testing.T) {
_, err := db.Insert(ctx, "A_tables", g.Map{
"ID": 1000,
"ACCOUNT_NAME": "map1",
"CREATED_TIME": gtime.Now().String(),
"CREATED_TIME": gtime.Now(),
})
t.AssertNil(err)
@ -249,7 +249,7 @@ func Test_DB_Insert(t *testing.T) {
result, err = db.Insert(ctx, "A_tables", g.Map{
"ID": 3000,
"ACCOUNT_NAME": "map3",
// "CREATED_TIME": gtime.Now().String(),
// "CREATED_TIME": gtime.Now(),
})
t.AssertNil(err)
n, _ = result.RowsAffected()
@ -320,12 +320,12 @@ func Test_DB_BatchInsert(t *testing.T) {
{
"ID": 400,
"ACCOUNT_NAME": "list_400",
// "CREATE_TIME": gtime.Now().String(),
// "CREATE_TIME": gtime.Now(),
},
{
"ID": 401,
"ACCOUNT_NAME": "list_401",
"CREATE_TIME": gtime.Now().String(),
"CREATE_TIME": gtime.Now(),
},
}, 1)
t.AssertNil(err)
@ -342,12 +342,12 @@ func Test_DB_BatchInsert(t *testing.T) {
g.Map{
"ID": 500,
"ACCOUNT_NAME": "500_batch_500",
"CREATE_TIME": gtime.Now().String(),
"CREATE_TIME": gtime.Now(),
},
g.Map{
"ID": 501,
"ACCOUNT_NAME": "501_batch_501",
// "CREATE_TIME": gtime.Now().String(),
// "CREATE_TIME": gtime.Now(),
},
}, 1)
t.AssertNil(err)
@ -363,7 +363,7 @@ func Test_DB_BatchInsert(t *testing.T) {
result, err := db.Insert(ctx, table, g.Map{
"ID": 600,
"ACCOUNT_NAME": "600_batch_600",
"CREATE_TIME": gtime.Now().String(),
"CREATE_TIME": gtime.Now(),
})
t.AssertNil(err)
n, _ := result.RowsAffected()

View File

@ -5,6 +5,6 @@ go 1.15
replace github.com/gogf/gf/v2 => ../../../
require (
gitee.com/chunanyong/dm v1.8.11
gitee.com/chunanyong/dm v1.8.10
github.com/gogf/gf/v2 v2.0.0
)

View File

@ -1,5 +1,5 @@
gitee.com/chunanyong/dm v1.8.11 h1:JPwiS1PqHObo4QFodruLR8WOhLP+7Y/EKGGu2BJ5SJI=
gitee.com/chunanyong/dm v1.8.11/go.mod h1:EPRJnuPFgbyOFgJ0TRYCTGzhq+ZT4wdyaj/GW/LLcNg=
gitee.com/chunanyong/dm v1.8.10 h1:9S1CKUggWHIea/GI7nr7S/DNMaxIilNFgfzdzKDx2+I=
gitee.com/chunanyong/dm v1.8.10/go.mod h1:EPRJnuPFgbyOFgJ0TRYCTGzhq+ZT4wdyaj/GW/LLcNg=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=

View File

@ -107,6 +107,8 @@ func createTable(table ...string) (name string) {
PASSWORD VARCHAR(32) NULL,
NICKNAME VARCHAR(45) NULL,
CREATE_TIME datetime NULL,
CREATED_AT datetimeoffset NULL,
UPDATED_AT datetimeoffset NULL,
PRIMARY KEY (ID))
`, name, name)); err != nil {
gtest.Fatal(err)
@ -125,7 +127,7 @@ func createInitTable(table ...string) (name string) {
"passport": fmt.Sprintf(`user_%d`, i),
"password": fmt.Sprintf(`pass_%d`, i),
"nickname": fmt.Sprintf(`name_%d`, i),
"create_time": gtime.Now().String(),
"create_time": gtime.Now(),
})
}
result, err := db.Insert(context.Background(), name, array.Slice())

View File

@ -120,7 +120,7 @@ func TestDoInsert(t *testing.T) {
"passport": fmt.Sprintf(`t%d`, i),
"password": fmt.Sprintf(`p%d`, i),
"nickname": fmt.Sprintf(`T%d`, i),
"create_time": gtime.Now().String(),
"create_time": gtime.Now(),
}
_, err := db.Insert(context.Background(), "t_user", data)
gtest.Assert(err, nil)
@ -137,7 +137,7 @@ func TestDoInsert(t *testing.T) {
"passport": fmt.Sprintf(`t%d`, i),
"password": fmt.Sprintf(`p%d`, i),
"nickname": fmt.Sprintf(`T%d`, i),
"create_time": gtime.Now().String(),
"create_time": gtime.Now(),
}
_, err := db.Save(context.Background(), "t_user", data, 10)
gtest.AssertNE(err, nil)
@ -192,7 +192,7 @@ func Test_DB_Insert(t *testing.T) {
"passport": "t1",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T1",
"create_time": gtime.Now().String(),
"create_time": gtime.Now(),
})
t.AssertNil(err)
@ -202,7 +202,7 @@ func Test_DB_Insert(t *testing.T) {
"passport": "t2",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "name_2",
"create_time": gtime.Now().String(),
"create_time": gtime.Now(),
})
t.AssertNil(err)
n, _ := result.RowsAffected()
@ -210,19 +210,19 @@ func Test_DB_Insert(t *testing.T) {
// struct
type User struct {
Id int `gconv:"id"`
Passport string `json:"passport"`
Password string `gconv:"password"`
Nickname string `gconv:"nickname"`
CreateTime string `json:"create_time"`
Id int `gconv:"id"`
Passport string `json:"passport"`
Password string `gconv:"password"`
Nickname string `gconv:"nickname"`
CreateTime *gtime.Time `json:"create_time"`
}
timeStr := gtime.Now().String()
timeNow := gtime.Now()
result, err = db.Insert(ctx, table, User{
Id: 3,
Passport: "user_3",
Password: "25d55ad283aa400af464c76d713c07ad",
Nickname: "name_3",
CreateTime: timeStr,
CreateTime: timeNow,
})
t.AssertNil(err)
n, _ = result.RowsAffected()
@ -235,16 +235,16 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["PASSPORT"].String(), "user_3")
t.Assert(one["PASSWORD"].String(), "25d55ad283aa400af464c76d713c07ad")
t.Assert(one["NICKNAME"].String(), "name_3")
t.Assert(one["CREATE_TIME"].GTime().String(), timeStr)
t.Assert(one["CREATE_TIME"].GTime(), timeNow)
// *struct
timeStr = gtime.Now().String()
timeNow = gtime.Now()
result, err = db.Insert(ctx, table, &User{
Id: 4,
Passport: "t4",
Password: "25d55ad283aa400af464c76d713c07ad",
Nickname: "name_4",
CreateTime: timeStr,
CreateTime: timeNow,
})
t.AssertNil(err)
n, _ = result.RowsAffected()
@ -256,24 +256,24 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["PASSPORT"].String(), "t4")
t.Assert(one["PASSWORD"].String(), "25d55ad283aa400af464c76d713c07ad")
t.Assert(one["NICKNAME"].String(), "name_4")
t.Assert(one["CREATE_TIME"].GTime().String(), timeStr)
t.Assert(one["CREATE_TIME"].GTime(), timeNow)
// batch with Insert
timeStr = gtime.Now().String()
timeNow = gtime.Now()
r, err := db.Insert(ctx, table, g.Slice{
g.Map{
"id": 200,
"passport": "t200",
"password": "25d55ad283aa400af464c76d71qw07ad",
"nickname": "T200",
"create_time": timeStr,
"create_time": timeNow,
},
g.Map{
"id": 300,
"passport": "t300",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T300",
"create_time": timeStr,
"create_time": timeNow,
},
})
t.AssertNil(err)
@ -286,7 +286,7 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["PASSPORT"].String(), "t200")
t.Assert(one["PASSWORD"].String(), "25d55ad283aa400af464c76d71qw07ad")
t.Assert(one["NICKNAME"].String(), "T200")
t.Assert(one["CREATE_TIME"].GTime().String(), timeStr)
t.Assert(one["CREATE_TIME"].GTime(), timeNow)
})
}
@ -360,14 +360,14 @@ func Test_DB_BatchInsert(t *testing.T) {
"passport": "t2",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "name_2",
"create_time": gtime.Now().String(),
"create_time": gtime.Now(),
},
{
"id": 3,
"passport": "user_3",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "name_3",
"create_time": gtime.Now().String(),
"create_time": gtime.Now(),
},
}, 1)
t.AssertNil(err)
@ -386,14 +386,14 @@ func Test_DB_BatchInsert(t *testing.T) {
"passport": "t2",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "name_2",
"create_time": gtime.Now().String(),
"create_time": gtime.Now(),
},
g.Map{
"id": 3,
"passport": "user_3",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "name_3",
"create_time": gtime.Now().String(),
"create_time": gtime.Now(),
},
}, 1)
t.AssertNil(err)
@ -410,7 +410,7 @@ func Test_DB_BatchInsert(t *testing.T) {
"passport": "t1",
"password": "p1",
"nickname": "T1",
"create_time": gtime.Now().String(),
"create_time": gtime.Now(),
})
t.AssertNil(err)
n, _ := result.RowsAffected()

View File

@ -1556,15 +1556,15 @@ func Test_Model_Option_Map(t *testing.T) {
t.Assert(n, 1)
_, err = db.Model(table).OmitEmptyData().Data(g.Map{"nickname": ""}).Where("id", 2).Update()
t.AssertNE(err, nil)
t.AssertNil(err)
r, err = db.Model(table).OmitEmpty().Data(g.Map{"nickname": "", "password": "123"}).Where("id", 3).Update()
t.AssertNil(err)
n, _ = r.RowsAffected()
t.Assert(n, 1)
_, err = db.Model(table).OmitEmpty().Fields("nickname").Data(g.Map{"nickname": "", "password": "123"}).Where("id", 4).Update()
t.AssertNE(err, nil)
_, err = db.Model(table).OmitEmpty().Fields("nickname", "password").Data(g.Map{"nickname": "", "password": "123", "passport": "123"}).Where("id", 4).Update()
t.AssertNil(err)
r, err = db.Model(table).OmitEmpty().
Fields("password").Data(g.Map{
@ -1639,7 +1639,7 @@ func Test_Model_FieldsEx(t *testing.T) {
defer dropTable(table)
// Select.
gtest.C(t, func(t *gtest.T) {
r, err := db.Model(table).FieldsEx("create_time, id").Where("id in (?)", g.Slice{1, 2}).Order("id asc").All()
r, err := db.Model(table).FieldsEx("create_time, created_at, updated_at, id").Where("id in (?)", g.Slice{1, 2}).Order("id asc").All()
t.AssertNil(err)
t.Assert(len(r), 2)
t.Assert(len(r[0]), 3)

View File

@ -117,7 +117,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
passport varchar(45) NULL,
password char(32) NULL,
nickname varchar(45) NULL,
create_time timestamp NULL,
create_time timestamp(6) NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, name,

View File

@ -17,15 +17,149 @@ import (
)
// CreateAt/UpdateAt/DeleteAt.
func Test_SoftCreateUpdateDeleteTime(t *testing.T) {
func Test_SoftCreateUpdateDeleteTimeMicroSecond(t *testing.T) {
table := "time_test_table_" + gtime.TimestampNanoStr()
if _, err := db.Exec(ctx, fmt.Sprintf(`
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
create_at datetime DEFAULT NULL,
update_at datetime DEFAULT NULL,
delete_at datetime DEFAULT NULL,
create_at datetime(6) DEFAULT NULL,
update_at datetime(6) DEFAULT NULL,
delete_at datetime(6) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table)); err != nil {
gtest.Error(err)
}
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
// Insert
dataInsert := g.Map{
"id": 1,
"name": "name_1",
}
r, err := db.Model(table).Data(dataInsert).Insert()
t.AssertNil(err)
n, _ := r.RowsAffected()
t.Assert(n, 1)
oneInsert, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(oneInsert["id"].Int(), 1)
t.Assert(oneInsert["name"].String(), "name_1")
t.Assert(oneInsert["delete_at"].String(), "")
t.AssertGE(oneInsert["create_at"].GTime().Timestamp(), gtime.Timestamp()-2)
t.AssertGE(oneInsert["update_at"].GTime().Timestamp(), gtime.Timestamp()-2)
// For time asserting purpose.
time.Sleep(2 * time.Second)
// Save
dataSave := g.Map{
"id": 1,
"name": "name_10",
}
r, err = db.Model(table).Data(dataSave).Save()
t.AssertNil(err)
n, _ = r.RowsAffected()
t.Assert(n, 2)
oneSave, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(oneSave["id"].Int(), 1)
t.Assert(oneSave["name"].String(), "name_10")
t.Assert(oneSave["delete_at"].String(), "")
t.Assert(oneSave["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp())
t.AssertNE(oneSave["update_at"].GTime().Timestamp(), oneInsert["update_at"].GTime().Timestamp())
t.AssertGE(oneSave["update_at"].GTime().Timestamp(), gtime.Timestamp()-2)
// For time asserting purpose.
time.Sleep(2 * time.Second)
// Update
dataUpdate := g.Map{
"name": "name_1000",
}
r, err = db.Model(table).Data(dataUpdate).WherePri(1).Update()
t.AssertNil(err)
n, _ = r.RowsAffected()
t.Assert(n, 1)
oneUpdate, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(oneUpdate["id"].Int(), 1)
t.Assert(oneUpdate["name"].String(), "name_1000")
t.Assert(oneUpdate["delete_at"].String(), "")
t.Assert(oneUpdate["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp())
t.AssertGE(oneUpdate["update_at"].GTime().Timestamp(), gtime.Timestamp()-2)
// Replace
dataReplace := g.Map{
"id": 1,
"name": "name_100",
}
r, err = db.Model(table).Data(dataReplace).Replace()
t.AssertNil(err)
n, _ = r.RowsAffected()
t.Assert(n, 2)
oneReplace, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(oneReplace["id"].Int(), 1)
t.Assert(oneReplace["name"].String(), "name_100")
t.Assert(oneReplace["delete_at"].String(), "")
t.AssertGE(oneReplace["create_at"].GTime().Timestamp(), oneInsert["create_at"].GTime().Timestamp())
t.AssertGE(oneReplace["update_at"].GTime().Timestamp(), oneInsert["update_at"].GTime().Timestamp())
// For time asserting purpose.
time.Sleep(2 * time.Second)
// Delete
r, err = db.Model(table).Delete("id", 1)
t.AssertNil(err)
n, _ = r.RowsAffected()
t.Assert(n, 1)
// Delete Select
one4, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(len(one4), 0)
one5, err := db.Model(table).Unscoped().WherePri(1).One()
t.AssertNil(err)
t.Assert(one5["id"].Int(), 1)
t.AssertGE(one5["delete_at"].GTime().Timestamp(), gtime.Timestamp()-2)
// Delete Count
i, err := db.Model(table).Count()
t.AssertNil(err)
t.Assert(i, 0)
i, err = db.Model(table).Unscoped().Count()
t.AssertNil(err)
t.Assert(i, 1)
// Delete Unscoped
r, err = db.Model(table).Unscoped().Delete("id", 1)
t.AssertNil(err)
n, _ = r.RowsAffected()
t.Assert(n, 1)
one6, err := db.Model(table).Unscoped().WherePri(1).One()
t.AssertNil(err)
t.Assert(len(one6), 0)
i, err = db.Model(table).Unscoped().Count()
t.AssertNil(err)
t.Assert(i, 0)
})
}
// CreateAt/UpdateAt/DeleteAt.
func Test_SoftCreateUpdateDeleteTimeSecond(t *testing.T) {
table := "time_test_table_" + gtime.TimestampNanoStr()
if _, err := db.Exec(ctx, fmt.Sprintf(`
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
create_at datetime(0) DEFAULT NULL,
update_at datetime(0) DEFAULT NULL,
delete_at datetime(0) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table)); err != nil {
@ -157,9 +291,9 @@ func Test_SoftCreatedUpdatedDeletedTime_Map(t *testing.T) {
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
created_at datetime DEFAULT NULL,
updated_at datetime DEFAULT NULL,
deleted_at datetime DEFAULT NULL,
created_at datetime(6) DEFAULT NULL,
updated_at datetime(6) DEFAULT NULL,
deleted_at datetime(6) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table)); err != nil {
@ -291,9 +425,9 @@ func Test_SoftCreatedUpdatedDeletedTime_Struct(t *testing.T) {
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
created_at datetime DEFAULT NULL,
updated_at datetime DEFAULT NULL,
deleted_at datetime DEFAULT NULL,
created_at datetime(6) DEFAULT NULL,
updated_at datetime(6) DEFAULT NULL,
deleted_at datetime(6) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table)); err != nil {
@ -431,9 +565,9 @@ func Test_SoftUpdateTime(t *testing.T) {
CREATE TABLE %s (
id int(11) NOT NULL,
num int(11) DEFAULT NULL,
create_at datetime DEFAULT NULL,
update_at datetime DEFAULT NULL,
delete_at datetime DEFAULT NULL,
create_at datetime(6) DEFAULT NULL,
update_at datetime(6) DEFAULT NULL,
delete_at datetime(6) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table)); err != nil {
@ -471,9 +605,9 @@ func Test_SoftUpdateTime_WithDO(t *testing.T) {
CREATE TABLE %s (
id int(11) NOT NULL,
num int(11) DEFAULT NULL,
created_at datetime DEFAULT NULL,
updated_at datetime DEFAULT NULL,
deleted_at datetime DEFAULT NULL,
created_at datetime(6) DEFAULT NULL,
updated_at datetime(6) DEFAULT NULL,
deleted_at datetime(6) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table)); err != nil {
@ -528,9 +662,9 @@ func Test_SoftDelete(t *testing.T) {
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
create_at datetime DEFAULT NULL,
update_at datetime DEFAULT NULL,
delete_at datetime DEFAULT NULL,
create_at datetime(6) DEFAULT NULL,
update_at datetime(6) DEFAULT NULL,
delete_at datetime(6) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table)); err != nil {
@ -596,9 +730,9 @@ func Test_SoftDelete_Join(t *testing.T) {
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
create_at datetime DEFAULT NULL,
update_at datetime DEFAULT NULL,
delete_at datetime DEFAULT NULL,
create_at datetime(6) DEFAULT NULL,
update_at datetime(6) DEFAULT NULL,
delete_at datetime(6) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table1)); err != nil {
@ -611,9 +745,9 @@ CREATE TABLE %s (
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
createat datetime DEFAULT NULL,
updateat datetime DEFAULT NULL,
deleteat datetime DEFAULT NULL,
createat datetime(6) DEFAULT NULL,
updateat datetime(6) DEFAULT NULL,
deleteat datetime(6) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table2)); err != nil {
@ -667,9 +801,9 @@ func Test_SoftDelete_WhereAndOr(t *testing.T) {
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
create_at datetime DEFAULT NULL,
update_at datetime DEFAULT NULL,
delete_at datetime DEFAULT NULL,
create_at datetime(6) DEFAULT NULL,
update_at datetime(6) DEFAULT NULL,
delete_at datetime(6) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table)); err != nil {
@ -709,9 +843,9 @@ func Test_CreateUpdateTime_Struct(t *testing.T) {
CREATE TABLE %s (
id int(11) NOT NULL,
name varchar(45) DEFAULT NULL,
create_at datetime DEFAULT NULL,
update_at datetime DEFAULT NULL,
delete_at datetime DEFAULT NULL,
create_at datetime(6) DEFAULT NULL,
update_at datetime(6) DEFAULT NULL,
delete_at datetime(6) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table)); err != nil {

View File

@ -532,8 +532,8 @@ func Test_Issue2338(t *testing.T) {
CREATE TABLE %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
nickname varchar(45) DEFAULT NULL COMMENT 'User Nickname',
create_at datetime DEFAULT NULL COMMENT 'Created Time',
update_at datetime DEFAULT NULL COMMENT 'Updated Time',
create_at datetime(6) DEFAULT NULL COMMENT 'Created Time',
update_at datetime(6) DEFAULT NULL COMMENT 'Updated Time',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table1,
@ -544,8 +544,8 @@ CREATE TABLE %s (
CREATE TABLE %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
nickname varchar(45) DEFAULT NULL COMMENT 'User Nickname',
create_at datetime DEFAULT NULL COMMENT 'Created Time',
update_at datetime DEFAULT NULL COMMENT 'Updated Time',
create_at datetime(6) DEFAULT NULL COMMENT 'Created Time',
update_at datetime(6) DEFAULT NULL COMMENT 'Updated Time',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table2,
@ -585,9 +585,9 @@ CREATE TABLE %s (
CREATE TABLE %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
nickname varchar(45) DEFAULT NULL COMMENT 'User Nickname',
create_at datetime DEFAULT NULL COMMENT 'Created Time',
update_at datetime DEFAULT NULL COMMENT 'Updated Time',
deleted_at datetime DEFAULT NULL COMMENT 'Deleted Time',
create_at datetime(6) DEFAULT NULL COMMENT 'Created Time',
update_at datetime(6) DEFAULT NULL COMMENT 'Updated Time',
deleted_at datetime(6) DEFAULT NULL COMMENT 'Deleted Time',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table1,
@ -598,9 +598,9 @@ CREATE TABLE %s (
CREATE TABLE %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
nickname varchar(45) DEFAULT NULL COMMENT 'User Nickname',
create_at datetime DEFAULT NULL COMMENT 'Created Time',
update_at datetime DEFAULT NULL COMMENT 'Updated Time',
deleted_at datetime DEFAULT NULL COMMENT 'Deleted Time',
create_at datetime(6) DEFAULT NULL COMMENT 'Created Time',
update_at datetime(6) DEFAULT NULL COMMENT 'Updated Time',
deleted_at datetime(6) DEFAULT NULL COMMENT 'Deleted Time',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table2,
@ -644,8 +644,8 @@ CREATE TABLE %s (
passport varchar(45) NOT NULL COMMENT 'User Passport',
password varchar(45) NOT NULL COMMENT 'User Password',
nickname varchar(45) NOT NULL COMMENT 'User Nickname',
create_at datetime DEFAULT NULL COMMENT 'Created Time',
update_at datetime DEFAULT NULL COMMENT 'Updated Time',
create_at datetime(6) DEFAULT NULL COMMENT 'Created Time',
update_at datetime(6) DEFAULT NULL COMMENT 'Updated Time',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, table,

View File

@ -3134,9 +3134,9 @@ func createTableForTimeZoneTest() string {
passport varchar(45) NULL,
password char(32) NULL,
nickname varchar(45) NULL,
created_at timestamp NULL,
updated_at timestamp NULL,
deleted_at timestamp NULL,
created_at timestamp(6) NULL,
updated_at timestamp(6) NULL,
deleted_at timestamp(6) NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`, tableName,

View File

@ -87,6 +87,10 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
)
}
if config.Namespace != "" {
source = fmt.Sprintf("%s search_path=%s", source, config.Namespace)
}
if config.Timezone != "" {
source = fmt.Sprintf("%s timezone=%s", source, config.Timezone)
}

View File

@ -152,7 +152,7 @@ func (d *Driver) TableFields(
if link, err = d.SlaveLink(usedSchema); err != nil {
return nil, err
}
result, err = d.DoSelect(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
result, err = d.DoSelect(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, d.QuoteWord(table)))
if err != nil {
return nil, err
}

View File

@ -33,15 +33,16 @@ var (
)
const (
TableSize = 10
TableName = "user"
TestSchema1 = "test1"
TestSchema2 = "test2"
TableNamePrefix = "gf_"
CreateTime = "2018-10-24 10:00:00"
DBGroupTest = "test"
DBGroupPrefix = "prefix"
DBGroupInvalid = "invalid"
TableSize = 10
TableName = "user"
TableNameWhichIsKeyword = "group"
TestSchema1 = "test1"
TestSchema2 = "test2"
TableNamePrefix = "gf_"
CreateTime = "2018-10-24 10:00:00"
DBGroupTest = "test"
DBGroupPrefix = "prefix"
DBGroupInvalid = "invalid"
)
func init() {
@ -125,7 +126,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
nickname VARCHAR(45),
create_time DATETIME
);
`, name,
`, db.GetCore().QuoteWord(name),
)); err != nil {
gtest.Fatal(err)
}

View File

@ -1571,3 +1571,39 @@ func Test_TableFields(t *testing.T) {
gtest.AssertNE(err, nil)
})
}
func Test_TableNameIsKeyword(t *testing.T) {
table := createInitTable(TableNameWhichIsKeyword)
defer dropTable(table)
_, err := db.Update(ctx, table, "create_time='2010-10-10 00:00:01'", "id=?", 1)
gtest.AssertNil(err)
gtest.C(t, func(t *gtest.T) {
id := 1
result, err := db.Model(table).Fields("*").Where("id = ?", id).All()
if err != nil {
gtest.Fatal(err)
}
type t_user struct {
Id int
Passport string
Password string
NickName string
CreateTime string
}
t_users := make([]t_user, 0)
err = result.Structs(&t_users)
if err != nil {
gtest.Fatal(err)
}
resultIntMap := result.MapKeyInt("id")
t.Assert(t_users[0].Id, resultIntMap[id]["id"])
t.Assert(t_users[0].Passport, resultIntMap[id]["passport"])
t.Assert(t_users[0].Password, resultIntMap[id]["password"])
t.Assert(t_users[0].NickName, resultIntMap[id]["nickname"])
t.Assert(t_users[0].CreateTime, resultIntMap[id]["create_time"])
})
}

View File

@ -47,6 +47,7 @@ func New(config *gredis.Config) *Redis {
Password: config.Pass,
DB: config.Db,
MaxRetries: defaultMaxRetries,
PoolSize: config.MaxActive,
MinIdleConns: config.MinIdle,
MaxConnAge: config.MaxConnLifetime,
IdleTimeout: config.IdleTimeout,

View File

@ -118,10 +118,10 @@ func (r GroupList) LPop(ctx context.Context, key string, count ...int) (*gvar.Va
// depending on the list's length.
//
// It returns:
// - When called without the count argument:
// the value of the last element, or nil when key does not exist.
// - When called with the count argument:
// list of popped elements, or nil when key does not exist.
// - When called without the count argument:
// the value of the last element, or nil when key does not exist.
// - When called with the count argument:
// list of popped elements, or nil when key does not exist.
//
// https://redis.io/commands/rpop
func (r GroupList) RPop(ctx context.Context, key string, count ...int) (*gvar.Var, error) {

View File

@ -64,10 +64,10 @@ func (r GroupSet) SIsMember(ctx context.Context, key string, member interface{})
// argument, the reply will consist of up to count members, depending on the set's cardinality.
//
// It returns:
// - When called without the count argument:
// Bulk string reply: the removed member, or nil when key does not exist.
// - When called with the count argument:
// Array reply: the removed members, or an empty array when key does not exist.
// - When called without the count argument:
// Bulk string reply: the removed member, or nil when key does not exist.
// - When called with the count argument:
// Array reply: the removed members, or an empty array when key does not exist.
//
// https://redis.io/commands/spop/
func (r GroupSet) SPop(ctx context.Context, key string, count ...int) (*gvar.Var, error) {
@ -86,10 +86,10 @@ func (r GroupSet) SPop(ctx context.Context, key string, count ...int) (*gvar.Var
// of the specified count.
//
// It returns:
// - Bulk string reply: without the additional count argument, the command returns a Bulk Reply with the
// randomly selected element, or nil when key does not exist.
// - Array reply: when the additional count argument is passed, the command returns an array of elements,
// or an empty array when key does not exist.
// - Bulk string reply: without the additional count argument, the command returns a Bulk Reply with the
// randomly selected element, or nil when key does not exist.
// - Array reply: when the additional count argument is passed, the command returns an array of elements,
// or an empty array when key does not exist.
//
// https://redis.io/commands/srandmember/
func (r GroupSet) SRandMember(ctx context.Context, key string, count ...int) (*gvar.Var, error) {
@ -212,7 +212,7 @@ func (r GroupSet) SUnion(ctx context.Context, key string, keys ...string) (gvar.
// SUnionStore is equal to SUnion, but instead of returning the resulting set, it is stored in destination.
//
// If destination already exists, it is overwritten.
// If destination already exists, it is overwritten.
//
// It returns the number of elements in the resulting set.
//

View File

@ -59,8 +59,8 @@ func (r GroupString) SetNX(ctx context.Context, key string, value interface{}) (
// SetEX sets key to hold the string value and set key to timeout after a given number of seconds.
// This command is equivalent to executing the following commands:
//
// SET myKey value
// EXPIRE myKey seconds
// SET myKey value
// EXPIRE myKey seconds
//
// SetEX is atomic, and can be reproduced by using the previous two commands inside an MULTI / EXEC block.
// It is provided as a faster alternative to the given sequence of operations, because this operation is very

View File

@ -0,0 +1,3 @@
# Service registrar and discovery
Please refer to certain sub folder.

View File

@ -8,16 +8,16 @@
package etcd
import (
"reflect"
"time"
etcd3 "go.etcd.io/etcd/client/v3"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/text/gstr"
etcd3 "go.etcd.io/etcd/client/v3"
)
var (
@ -45,7 +45,7 @@ const (
)
// New creates and returns a new etcd registry.
func New(address string, option ...Option) *Registry {
func New(address string, option ...Option) gsvc.Registry {
endpoints := gstr.SplitAndTrim(address, ",")
if len(endpoints) == 0 {
panic(gerror.NewCodef(gcode.CodeInvalidParameter, `invalid etcd address "%s"`, address))
@ -84,31 +84,22 @@ func extractResponseToServices(res *etcd3.GetResponse) ([]gsvc.Service, error) {
return nil, nil
}
var (
services []gsvc.Service
serviceKey string
serviceMap = make(map[string]*gsvc.LocalService)
services []gsvc.Service
servicePrefixMap = make(map[string]*Service)
)
for _, kv := range res.Kvs {
service, err := gsvc.NewServiceWithKV(string(kv.Key), string(kv.Value))
service, err := gsvc.NewServiceWithKV(
string(kv.Key), string(kv.Value),
)
if err != nil {
return services, err
}
localService, ok := service.(*gsvc.LocalService)
if !ok {
return nil, gerror.Newf(
`service from "gsvc.NewServiceWithKV" is not "*gsvc.LocalService", but "%s"`,
reflect.TypeOf(service),
)
}
if localService != nil {
serviceKey = localService.GetPrefix()
var localServiceInMap *gsvc.LocalService
if localServiceInMap, ok = serviceMap[serviceKey]; ok {
localServiceInMap.Endpoints = append(localServiceInMap.Endpoints, localService.Endpoints...)
} else {
serviceMap[serviceKey] = localService
services = append(services, service)
}
s := NewService(service)
if v, ok := servicePrefixMap[service.GetPrefix()]; ok {
v.Endpoints = append(v.Endpoints, service.GetEndpoints()...)
} else {
servicePrefixMap[s.GetPrefix()] = s
services = append(services, s)
}
}
return services, nil

View File

@ -9,11 +9,14 @@ package etcd
import (
"context"
"github.com/gogf/gf/v2/net/gsvc"
etcd3 "go.etcd.io/etcd/client/v3"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/text/gstr"
)
// Search is the etcd discovery search function.
// Search searches and returns services with specified condition.
func (r *Registry) Search(ctx context.Context, in gsvc.SearchInput) ([]gsvc.Service, error) {
if in.Prefix == "" && in.Name != "" {
in.Prefix = gsvc.NewServiceWithName(in.Name).GetPrefix()
@ -29,20 +32,31 @@ func (r *Registry) Search(ctx context.Context, in gsvc.SearchInput) ([]gsvc.Serv
}
// Service filter.
filteredServices := make([]gsvc.Service, 0)
for _, v := range services {
if in.Name != "" && in.Name != v.GetName() {
for _, service := range services {
if in.Prefix != "" && !gstr.HasPrefix(service.GetKey(), in.Prefix) {
continue
}
if in.Version != "" && in.Version != v.GetVersion() {
if in.Name != "" && service.GetName() != in.Name {
continue
}
service := v
filteredServices = append(filteredServices, service)
if in.Version != "" && service.GetVersion() != in.Version {
continue
}
if len(in.Metadata) != 0 {
m1 := gmap.NewStrAnyMapFrom(in.Metadata)
m2 := gmap.NewStrAnyMapFrom(service.GetMetadata())
if !m1.IsSubOf(m2) {
continue
}
}
resultItem := service
filteredServices = append(filteredServices, resultItem)
}
return filteredServices, nil
}
// Watch is the etcd discovery watch function.
// Watch watches specified condition changes.
// The `key` is the prefix of service key.
func (r *Registry) Watch(ctx context.Context, key string) (gsvc.Watcher, error) {
return newWatcher(key, r.client)
}

View File

@ -9,13 +9,16 @@ package etcd
import (
"context"
etcd3 "go.etcd.io/etcd/client/v3"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gsvc"
etcd3 "go.etcd.io/etcd/client/v3"
)
// Register implements the gsvc.Register interface.
// Register registers `service` to Registry.
// Note that it returns a new Service if it changes the input Service with custom one.
func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Service, error) {
service = NewService(service)
r.lease = etcd3.NewLease(r.client)
grant, err := r.lease.Grant(ctx, int64(r.keepaliveTTL.Seconds()))
if err != nil {
@ -33,7 +36,7 @@ func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Ser
key, value, grant.ID,
)
}
r.logger.Infof(
r.logger.Debugf(
ctx,
`etcd put success with key "%s", value "%s", lease "%d"`,
key, value, grant.ID,
@ -46,7 +49,7 @@ func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Ser
return service, nil
}
// Deregister implements the gsvc.Deregister interface.
// Deregister off-lines and removes `service` from the Registry.
func (r *Registry) Deregister(ctx context.Context, service gsvc.Service) error {
_, err := r.client.Delete(ctx, service.GetKey())
if r.lease != nil {

View File

@ -0,0 +1,64 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package etcd
import (
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/net/gsvc"
)
// Service wrapper.
type Service struct {
gsvc.Service
Endpoints gsvc.Endpoints
Metadata gsvc.Metadata
}
// NewService creates and returns local Service from gsvc.Service interface object.
func NewService(service gsvc.Service) *Service {
s, ok := service.(*Service)
if ok {
if s.Endpoints == nil {
s.Endpoints = make(gsvc.Endpoints, 0)
}
if s.Metadata == nil {
s.Metadata = make(gsvc.Metadata)
}
return s
}
s = &Service{
Service: service,
Endpoints: make(gsvc.Endpoints, 0),
Metadata: make(gsvc.Metadata),
}
if len(service.GetEndpoints()) > 0 {
s.Endpoints = service.GetEndpoints()
}
if len(service.GetMetadata()) > 0 {
s.Metadata = service.GetMetadata()
}
return s
}
// GetMetadata returns the Metadata map of service.
// The Metadata is key-value pair map specifying extra attributes of a service.
func (s *Service) GetMetadata() gsvc.Metadata {
return s.Metadata
}
// GetEndpoints returns the Endpoints of service.
// The Endpoints contain multiple host/port information of service.
func (s *Service) GetEndpoints() gsvc.Endpoints {
return s.Endpoints
}
// GetValue formats and returns the value of the service.
// The result value is commonly used for key-value registrar server.
func (s *Service) GetValue() string {
b, _ := gjson.Marshal(s.Metadata)
return string(b)
}

View File

@ -9,8 +9,9 @@ package etcd
import (
"context"
"github.com/gogf/gf/v2/net/gsvc"
etcd3 "go.etcd.io/etcd/client/v3"
"github.com/gogf/gf/v2/net/gsvc"
)
var (
@ -46,6 +47,7 @@ func (w *watcher) Proceed() ([]gsvc.Service, error) {
case <-w.ctx.Done():
return nil, w.ctx.Err()
case <-w.watchChan:
// It retrieves, merges and returns all services by prefix if any changes.
return w.getServicesByPrefix()
}
}

View File

@ -0,0 +1,145 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package etcd_test
import (
"testing"
"github.com/gogf/gf/contrib/registry/etcd/v2"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/guid"
)
func TestRegistry(t *testing.T) {
var (
ctx = gctx.GetInitCtx()
registry = etcd.New(`127.0.0.1:2379`)
)
svc := &gsvc.LocalService{
Name: guid.S(),
Endpoints: gsvc.NewEndpoints("127.0.0.1:8888"),
Metadata: map[string]interface{}{
"protocol": "https",
},
}
gtest.C(t, func(t *gtest.T) {
registered, err := registry.Register(ctx, svc)
t.AssertNil(err)
t.Assert(registered.GetName(), svc.GetName())
})
// Search by name.
gtest.C(t, func(t *gtest.T) {
result, err := registry.Search(ctx, gsvc.SearchInput{
Name: svc.Name,
})
t.AssertNil(err)
t.Assert(len(result), 1)
t.Assert(result[0].GetName(), svc.Name)
})
// Search by prefix.
gtest.C(t, func(t *gtest.T) {
result, err := registry.Search(ctx, gsvc.SearchInput{
Prefix: svc.GetPrefix(),
})
t.AssertNil(err)
t.Assert(len(result), 1)
t.Assert(result[0].GetName(), svc.Name)
})
// Search by metadata.
gtest.C(t, func(t *gtest.T) {
result, err := registry.Search(ctx, gsvc.SearchInput{
Name: svc.GetName(),
Metadata: map[string]interface{}{
"protocol": "https",
},
})
t.AssertNil(err)
t.Assert(len(result), 1)
t.Assert(result[0].GetName(), svc.Name)
})
gtest.C(t, func(t *gtest.T) {
result, err := registry.Search(ctx, gsvc.SearchInput{
Name: svc.GetName(),
Metadata: map[string]interface{}{
"protocol": "grpc",
},
})
t.AssertNil(err)
t.Assert(len(result), 0)
})
gtest.C(t, func(t *gtest.T) {
err := registry.Deregister(ctx, svc)
t.AssertNil(err)
})
}
func TestWatch(t *testing.T) {
var (
ctx = gctx.GetInitCtx()
registry = etcd.New(`127.0.0.1:2379`)
)
svc1 := &gsvc.LocalService{
Name: guid.S(),
Endpoints: gsvc.NewEndpoints("127.0.0.1:8888"),
Metadata: map[string]interface{}{
"protocol": "https",
},
}
gtest.C(t, func(t *gtest.T) {
registered, err := registry.Register(ctx, svc1)
t.AssertNil(err)
t.Assert(registered.GetName(), svc1.GetName())
})
gtest.C(t, func(t *gtest.T) {
watcher, err := registry.Watch(ctx, svc1.GetPrefix())
t.AssertNil(err)
// Register another service.
svc2 := &gsvc.LocalService{
Name: svc1.Name,
Endpoints: gsvc.NewEndpoints("127.0.0.1:9999"),
}
registered, err := registry.Register(ctx, svc2)
t.AssertNil(err)
t.Assert(registered.GetName(), svc2.GetName())
// Watch and retrieve the service changes:
// svc1 and svc2 is the same service name, which has 2 endpoints.
proceedResult, err := watcher.Proceed()
t.AssertNil(err)
t.Assert(len(proceedResult), 1)
t.Assert(
proceedResult[0].GetEndpoints(),
gsvc.Endpoints{svc1.GetEndpoints()[0], svc2.GetEndpoints()[0]},
)
// Watch and retrieve the service changes:
// left only svc1, which means this service has only 1 endpoint.
err = registry.Deregister(ctx, svc2)
t.AssertNil(err)
proceedResult, err = watcher.Proceed()
t.AssertNil(err)
t.Assert(
proceedResult[0].GetEndpoints(),
gsvc.Endpoints{svc1.GetEndpoints()[0]},
)
t.AssertNil(watcher.Close())
})
gtest.C(t, func(t *gtest.T) {
err := registry.Deregister(ctx, svc1)
t.AssertNil(err)
})
}

View File

@ -0,0 +1,77 @@
# GoFrame File Registry
Use `file` as service registration and discovery management.
## Installation
```
go get -u -v github.com/gogf/gf/contrib/registry/file/v2
```
suggested using `go.mod`:
```
require github.com/gogf/gf/contrib/registry/file/v2 latest
```
## Example
### Reference example
[server](example/registry/file/server/main.go)
```go
package main
import (
"github.com/gogf/gf/contrib/registry/file/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/gfile"
)
func main() {
gsvc.SetRegistry(file.New(gfile.Temp("gsvc")))
s := g.Server(`hello.svc`)
s.BindHandler("/", func(r *ghttp.Request) {
g.Log().Info(r.Context(), `request received`)
r.Response.Write(`Hello world`)
})
s.Run()
}
```
[client](example/registry/file/client/main.go)
```go
package main
import (
"fmt"
"time"
"github.com/gogf/gf/contrib/registry/file/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gfile"
)
func main() {
gsvc.SetRegistry(file.New(gfile.Temp("gsvc")))
client := g.Client()
for i := 0; i < 100; i++ {
res, err := client.Get(gctx.New(), `http://hello.svc/`)
if err != nil {
panic(err)
}
fmt.Println(res.ReadAllString())
res.Close()
time.Sleep(time.Second)
}
}
```

View File

@ -0,0 +1,42 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package file implements service Registry and Discovery using file.
package file
import (
"time"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/gfile"
)
var (
_ gsvc.Registry = &Registry{}
)
const (
updateAtKey = "UpdateAt"
serviceTTL = 20 * time.Second
serviceUpdateInterval = 10 * time.Second
defaultSeparator = "#"
)
// Registry implements interface Registry using file.
// This implement is usually for testing only.
type Registry struct {
path string // Local storing folder path for Services.
}
// New creates and returns a gsvc.Registry implements using file.
func New(path string) gsvc.Registry {
if !gfile.Exists(path) {
_ = gfile.Mkdir(path)
}
return &Registry{
path: path,
}
}

View File

@ -0,0 +1,134 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package file
import (
"context"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gfsnotify"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gstr"
)
// Search searches and returns services with specified condition.
func (r *Registry) Search(ctx context.Context, in gsvc.SearchInput) (result []gsvc.Service, err error) {
services, err := r.getServices(ctx)
if err != nil {
return nil, err
}
for _, service := range services {
if in.Prefix != "" && !gstr.HasPrefix(service.GetKey(), in.Prefix) {
continue
}
if in.Name != "" && service.GetName() != in.Name {
continue
}
if in.Version != "" && service.GetVersion() != in.Version {
continue
}
if len(in.Metadata) != 0 {
m1 := gmap.NewStrAnyMapFrom(in.Metadata)
m2 := gmap.NewStrAnyMapFrom(service.GetMetadata())
if !m1.IsSubOf(m2) {
continue
}
}
resultItem := service
result = append(result, resultItem)
}
result = r.mergeServices(result)
return
}
// Watch watches specified condition changes.
// The `key` is the prefix of service key.
func (r *Registry) Watch(ctx context.Context, key string) (watcher gsvc.Watcher, err error) {
fileWatcher := &Watcher{
prefix: key,
discovery: r,
ch: make(chan gsvc.Service, 100),
}
_, err = gfsnotify.Add(r.path, func(event *gfsnotify.Event) {
if event.IsChmod() {
return
}
if !gstr.HasPrefix(gfile.Basename(event.Path), r.getServiceKeyForFile(key)) {
return
}
service, err := r.getServiceByFilePath(event.Path)
if err != nil {
return
}
fileWatcher.ch <- service
})
return fileWatcher, err
}
func (r *Registry) getServices(ctx context.Context) (services []gsvc.Service, err error) {
filePaths, err := gfile.ScanDirFile(r.path, "*", false)
if err != nil {
return nil, err
}
for _, filePath := range filePaths {
s, e := r.getServiceByFilePath(filePath)
if e != nil {
return nil, e
}
// Check service TTL.
var (
updateAt = s.GetMetadata().Get(updateAtKey).GTime()
nowTime = gtime.Now()
subDuration = nowTime.Sub(updateAt)
)
if updateAt.IsZero() || subDuration > serviceTTL {
g.Log().Debugf(
ctx,
`service "%s" is expired, update at: %s, current: %s, sub duration: %s`,
s.GetKey(), updateAt.String(), nowTime.String(), subDuration.String(),
)
continue
}
services = append(services, s)
}
services = r.mergeServices(services)
return
}
func (r *Registry) getServiceByFilePath(filePath string) (gsvc.Service, error) {
var (
fileName = gfile.Basename(filePath)
fileContent = gfile.GetContents(filePath)
serviceKey = gstr.Replace(fileName, defaultSeparator, gsvc.DefaultSeparator)
)
serviceKey = gsvc.DefaultSeparator + serviceKey
return gsvc.NewServiceWithKV(serviceKey, fileContent)
}
func (r *Registry) mergeServices(services []gsvc.Service) []gsvc.Service {
if len(services) == 0 {
return services
}
var (
servicePrefixMap = make(map[string]*Service)
mergeServices = make([]gsvc.Service, 0)
)
for _, service := range services {
if v, ok := servicePrefixMap[service.GetPrefix()]; ok {
v.Endpoints = append(v.Endpoints, service.GetEndpoints()...)
} else {
s := NewService(service)
servicePrefixMap[s.GetPrefix()] = s
mergeServices = append(mergeServices, s)
}
}
return mergeServices
}

View File

@ -0,0 +1,62 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package file
import (
"context"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/os/gtimer"
"github.com/gogf/gf/v2/text/gstr"
)
// Register registers `service` to Registry.
// Note that it returns a new Service if it changes the input Service with custom one.
func (r *Registry) Register(ctx context.Context, service gsvc.Service) (registered gsvc.Service, err error) {
service = NewService(service)
service.GetMetadata().Set(updateAtKey, gtime.Now())
var (
filePath = r.getServiceFilePath(service)
fileContent = service.GetValue()
)
err = gfile.PutContents(filePath, fileContent)
if err == nil {
gtimer.Add(ctx, serviceUpdateInterval, func(ctx context.Context) {
if !gfile.Exists(filePath) {
gtimer.Exit()
}
// Update TTL in timer.
service, _ = r.getServiceByFilePath(filePath)
if service != nil {
service.GetMetadata().Set(updateAtKey, gtime.Now())
}
_ = gfile.PutContents(filePath, service.GetValue())
})
}
return service, err
}
// Deregister off-lines and removes `service` from the Registry.
func (r *Registry) Deregister(ctx context.Context, service gsvc.Service) error {
return gfile.Remove(r.getServiceFilePath(service))
}
func (r *Registry) getServiceFilePath(service gsvc.Service) string {
return gfile.Join(r.path, r.getServiceFileName(service))
}
func (r *Registry) getServiceFileName(service gsvc.Service) string {
return r.getServiceKeyForFile(service.GetKey())
}
func (r *Registry) getServiceKeyForFile(key string) string {
key = gstr.Replace(key, gsvc.DefaultSeparator, defaultSeparator)
key = gstr.Trim(key, defaultSeparator)
return key
}

View File

@ -0,0 +1,64 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package file
import (
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/net/gsvc"
)
// Service wrapper.
type Service struct {
gsvc.Service
Endpoints gsvc.Endpoints
Metadata gsvc.Metadata
}
// NewService creates and returns local Service from gsvc.Service interface object.
func NewService(service gsvc.Service) *Service {
s, ok := service.(*Service)
if ok {
if s.Endpoints == nil {
s.Endpoints = make(gsvc.Endpoints, 0)
}
if s.Metadata == nil {
s.Metadata = make(gsvc.Metadata)
}
return s
}
s = &Service{
Service: service,
Endpoints: make(gsvc.Endpoints, 0),
Metadata: make(gsvc.Metadata),
}
if len(service.GetEndpoints()) > 0 {
s.Endpoints = service.GetEndpoints()
}
if len(service.GetMetadata()) > 0 {
s.Metadata = service.GetMetadata()
}
return s
}
// GetMetadata returns the Metadata map of service.
// The Metadata is key-value pair map specifying extra attributes of a service.
func (s *Service) GetMetadata() gsvc.Metadata {
return s.Metadata
}
// GetEndpoints returns the Endpoints of service.
// The Endpoints contain multiple host/port information of service.
func (s *Service) GetEndpoints() gsvc.Endpoints {
return s.Endpoints
}
// GetValue formats and returns the value of the service.
// The result value is commonly used for key-value registrar server.
func (s *Service) GetValue() string {
b, _ := gjson.Marshal(s.Metadata)
return string(b)
}

View File

@ -0,0 +1,35 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package file
import (
"context"
"github.com/gogf/gf/v2/net/gsvc"
)
// Watcher for file changes watch.
type Watcher struct {
prefix string // Watched prefix key, not file name prefix.
discovery gsvc.Discovery // Service discovery.
ch chan gsvc.Service // Changes that caused by inotify.
}
// Proceed proceeds watch in blocking way.
// It returns all complete services that watched by `key` if any change.
func (w *Watcher) Proceed() (services []gsvc.Service, err error) {
<-w.ch
return w.discovery.Search(context.Background(), gsvc.SearchInput{
Prefix: w.prefix,
})
}
// Close closes the watcher.
func (w *Watcher) Close() error {
close(w.ch)
return nil
}

View File

@ -0,0 +1,150 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package file_test
import (
"testing"
"github.com/gogf/gf/contrib/registry/file/v2"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/guid"
)
func TestRegistry(t *testing.T) {
var (
ctx = gctx.GetInitCtx()
path = gfile.Temp(guid.S())
registry = file.New(path)
)
defer gfile.Remove(path)
svc := &gsvc.LocalService{
Name: guid.S(),
Endpoints: gsvc.NewEndpoints("127.0.0.1:8888"),
Metadata: map[string]interface{}{
"protocol": "https",
},
}
gtest.C(t, func(t *gtest.T) {
registered, err := registry.Register(ctx, svc)
t.AssertNil(err)
t.Assert(registered.GetName(), svc.GetName())
})
// Search by name.
gtest.C(t, func(t *gtest.T) {
result, err := registry.Search(ctx, gsvc.SearchInput{
Name: svc.Name,
})
t.AssertNil(err)
t.Assert(len(result), 1)
t.Assert(result[0].GetName(), svc.Name)
})
// Search by prefix.
gtest.C(t, func(t *gtest.T) {
result, err := registry.Search(ctx, gsvc.SearchInput{
Prefix: svc.GetPrefix(),
})
t.AssertNil(err)
t.Assert(len(result), 1)
t.Assert(result[0].GetName(), svc.Name)
})
// Search by metadata.
gtest.C(t, func(t *gtest.T) {
result, err := registry.Search(ctx, gsvc.SearchInput{
Name: svc.GetName(),
Metadata: map[string]interface{}{
"protocol": "https",
},
})
t.AssertNil(err)
t.Assert(len(result), 1)
t.Assert(result[0].GetName(), svc.Name)
})
gtest.C(t, func(t *gtest.T) {
result, err := registry.Search(ctx, gsvc.SearchInput{
Name: svc.GetName(),
Metadata: map[string]interface{}{
"protocol": "grpc",
},
})
t.AssertNil(err)
t.Assert(len(result), 0)
})
gtest.C(t, func(t *gtest.T) {
err := registry.Deregister(ctx, svc)
t.AssertNil(err)
})
}
func TestWatch(t *testing.T) {
var (
ctx = gctx.GetInitCtx()
path = gfile.Temp(guid.S())
registry = file.New(path)
)
defer gfile.Remove(path)
svc1 := &gsvc.LocalService{
Name: guid.S(),
Endpoints: gsvc.NewEndpoints("127.0.0.1:8888"),
Metadata: map[string]interface{}{
"protocol": "https",
},
}
gtest.C(t, func(t *gtest.T) {
registered, err := registry.Register(ctx, svc1)
t.AssertNil(err)
t.Assert(registered.GetName(), svc1.GetName())
})
gtest.C(t, func(t *gtest.T) {
watcher, err := registry.Watch(ctx, svc1.GetPrefix())
t.AssertNil(err)
// Register another service.
svc2 := &gsvc.LocalService{
Name: svc1.Name,
Endpoints: gsvc.NewEndpoints("127.0.0.1:9999"),
}
registered, err := registry.Register(ctx, svc2)
t.AssertNil(err)
t.Assert(registered.GetName(), svc2.GetName())
// Watch and retrieve the service changes:
// svc1 and svc2 is the same service name, which has 2 endpoints.
proceedResult, err := watcher.Proceed()
t.AssertNil(err)
t.Assert(len(proceedResult), 1)
t.Assert(
proceedResult[0].GetEndpoints(),
gsvc.Endpoints{svc1.GetEndpoints()[0], svc2.GetEndpoints()[0]},
)
// Watch and retrieve the service changes:
// left only svc1, which means this service has only 1 endpoint.
err = registry.Deregister(ctx, svc2)
t.AssertNil(err)
proceedResult, err = watcher.Proceed()
t.AssertNil(err)
t.Assert(
proceedResult[0].GetEndpoints(),
gsvc.Endpoints{svc1.GetEndpoints()[0]},
)
})
gtest.C(t, func(t *gtest.T) {
err := registry.Deregister(ctx, svc1)
t.AssertNil(err)
})
}

View File

@ -0,0 +1,7 @@
module github.com/gogf/gf/contrib/registry/file/v2
go 1.15
require github.com/gogf/gf/v2 v2.0.0
replace github.com/gogf/gf/v2 => ../../../

View File

@ -0,0 +1,83 @@
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0=
go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU=
go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y=
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -4,7 +4,7 @@ go 1.15
require (
github.com/gogf/gf/v2 v2.0.0
github.com/polarismesh/polaris-go v1.2.0-beta.3
github.com/polarismesh/polaris-go v1.3.0
)
replace github.com/gogf/gf/v2 => ../../../

View File

@ -229,6 +229,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polarismesh/polaris-go v1.2.0-beta.3 h1:sqN50VGign37xVFp9nrN1RoLXacsB0QfRYUtuCWMJGI=
github.com/polarismesh/polaris-go v1.2.0-beta.3/go.mod h1:HsN0ierETIujHpmnnYJ3qkwQw4QGAECuHvBZTDaw1tI=
github.com/polarismesh/polaris-go v1.3.0 h1:KZKX//ow4OPPoS5+s7h07ptprg+2AcNVGrN6WakC9QM=
github.com/polarismesh/polaris-go v1.3.0/go.mod h1:HsN0ierETIujHpmnnYJ3qkwQw4QGAECuHvBZTDaw1tI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=

View File

@ -10,11 +10,12 @@ package polaris
import (
"time"
"github.com/polarismesh/polaris-go"
"github.com/polarismesh/polaris-go/pkg/config"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/os/glog"
"github.com/polarismesh/polaris-go"
"github.com/polarismesh/polaris-go/pkg/config"
)
var (
@ -46,16 +47,19 @@ type options struct {
// To show service is healthy or not. Default value is True.
Healthy bool
// Heartbeat enable .Not in polaris . Default value is True.
// Deprecated: Use RegisterInstance instead.
// Service registration is performed synchronously,
// and heartbeat reporting is automatically performed
// Heartbeat enable .Not in polaris. Default value is True.
Heartbeat bool
// To show service is isolate or not. Default value is False.
Isolate bool
// TTL timeout. if node needs to use heartbeat to report,required. If not set,server will throw ErrorCode-400141
// TTL timeout. if the node needs to use a heartbeat to report, required. If not set,server will throw ErrorCode-400141
TTL int
// optional, Timeout for single query. Default value is global config
// Timeout for the single query. Default value is global config
// Total is (1+RetryCount) * Timeout
Timeout time.Duration
@ -123,6 +127,7 @@ func WithRetryCount(retryCount int) Option {
}
// WithHeartbeat with the Heartbeat option.
// Deprecated remove in v2.4.0
func WithHeartbeat(heartbeat bool) Option {
return func(o *options) { o.Heartbeat = heartbeat }
}
@ -133,7 +138,7 @@ func WithLogger(logger glog.ILogger) Option {
}
// New create a new registry.
func New(provider polaris.ProviderAPI, consumer polaris.ConsumerAPI, opts ...Option) (r *Registry) {
func New(provider polaris.ProviderAPI, consumer polaris.ConsumerAPI, opts ...Option) gsvc.Registry {
op := options{
Namespace: "default",
ServiceToken: "",
@ -160,7 +165,7 @@ func New(provider polaris.ProviderAPI, consumer polaris.ConsumerAPI, opts ...Opt
}
// NewWithConfig new a registry with config.
func NewWithConfig(conf config.Configuration, opts ...Option) (r *Registry) {
func NewWithConfig(conf config.Configuration, opts ...Option) gsvc.Registry {
provider, err := polaris.NewProviderAPIByConfig(conf)
if err != nil {
panic(err)

View File

@ -11,10 +11,13 @@ import (
"fmt"
"strings"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/util/gconv"
"github.com/polarismesh/polaris-go"
"github.com/polarismesh/polaris-go/pkg/model"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
// Search returns the service instances in memory according to the service name.
@ -38,7 +41,29 @@ func (r *Registry) Search(ctx context.Context, in gsvc.SearchInput) ([]gsvc.Serv
return nil, err
}
serviceInstances := instancesToServiceInstances(instancesResponse.GetInstances())
return serviceInstances, nil
// Service filter.
filteredServices := make([]gsvc.Service, 0)
for _, service := range serviceInstances {
if in.Prefix != "" && !gstr.HasPrefix(service.GetKey(), in.Prefix) {
continue
}
if in.Name != "" && service.GetName() != in.Name {
continue
}
if in.Version != "" && service.GetVersion() != in.Version {
continue
}
if len(in.Metadata) != 0 {
m1 := gmap.NewStrAnyMapFrom(in.Metadata)
m2 := gmap.NewStrAnyMapFrom(service.GetMetadata())
if !m1.IsSubOf(m2) {
continue
}
}
resultItem := service
filteredServices = append(filteredServices, resultItem)
}
return filteredServices, nil
}
// Watch creates a watcher according to the service name.

View File

@ -10,16 +10,17 @@ import (
"context"
"time"
"github.com/polarismesh/polaris-go"
"github.com/polarismesh/polaris-go/pkg/model"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/polarismesh/polaris-go"
"github.com/polarismesh/polaris-go/pkg/model"
)
// Register the registration.
func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Service, error) {
// Replace input service to custom service type.
// Replace input service to custom service types.
service = &Service{
Service: service,
}
@ -47,8 +48,9 @@ func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Ser
rmd[k] = v
}
}
// Register
registeredService, err := r.provider.Register(
// Register RegisterInstance Service registration is performed synchronously,
// and heartbeat reporting is automatically performed
registeredService, err := r.provider.RegisterInstance(
&polaris.InstanceRegisterRequest{
InstanceRegisterRequest: model.InstanceRegisterRequest{
Service: service.GetPrefix(),
@ -72,7 +74,7 @@ func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Ser
return nil, err
}
if r.opt.Heartbeat {
r.doHeartBeat(ctx, registeredService.InstanceID, service, endpoint)
// r.doHeartBeat(ctx, registeredService.InstanceID, service, endpoint)
}
ids = append(ids, registeredService.InstanceID)
}
@ -83,7 +85,7 @@ func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Ser
// Deregister the registration.
func (r *Registry) Deregister(ctx context.Context, service gsvc.Service) error {
r.c <- struct{}{}
// r.c <- struct{}{}
var (
err error
split = gstr.Split(service.(*Service).ID, instanceIDSeparator)
@ -111,6 +113,7 @@ func (r *Registry) Deregister(ctx context.Context, service gsvc.Service) error {
return nil
}
// Deprecated .
func (r *Registry) doHeartBeat(ctx context.Context, instanceID string, service gsvc.Service, endpoint gsvc.Endpoint) {
go func() {
ticker := time.NewTicker(time.Second * time.Duration(r.opt.TTL))

View File

@ -1,3 +1,9 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package polaris
import (

View File

@ -9,14 +9,16 @@ package zookeeper
import (
"github.com/go-zookeeper/zk"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gsvc"
"golang.org/x/sync/singleflight"
"time"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gsvc"
)
var _ gsvc.Registry = &Registry{}
// Content for custom service Marshal/Unmarshal.
type Content struct {
Key string
Value string

View File

@ -8,13 +8,16 @@ package zookeeper
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gsvc"
"path"
"strings"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gsvc"
"github.com/gogf/gf/v2/text/gstr"
)
// Search is the etcd discovery search function.
// Search searches and returns services with specified condition.
func (r *Registry) Search(_ context.Context, in gsvc.SearchInput) ([]gsvc.Service, error) {
prefix := strings.TrimPrefix(strings.ReplaceAll(in.Prefix, "/", "-"), "-")
instances, err, _ := r.group.Do(prefix, func() (interface{}, error) {
@ -62,10 +65,33 @@ func (r *Registry) Search(_ context.Context, in gsvc.SearchInput) ([]gsvc.Servic
"Error with group do",
)
}
return instances.([]gsvc.Service), nil
// Service filter.
filteredServices := make([]gsvc.Service, 0)
for _, service := range instances.([]gsvc.Service) {
if in.Prefix != "" && !gstr.HasPrefix(service.GetKey(), in.Prefix) {
continue
}
if in.Name != "" && service.GetName() != in.Name {
continue
}
if in.Version != "" && service.GetVersion() != in.Version {
continue
}
if len(in.Metadata) != 0 {
m1 := gmap.NewStrAnyMapFrom(in.Metadata)
m2 := gmap.NewStrAnyMapFrom(service.GetMetadata())
if !m1.IsSubOf(m2) {
continue
}
}
resultItem := service
filteredServices = append(filteredServices, resultItem)
}
return filteredServices, nil
}
// Watch is the etcd discovery watch function.
// Watch watches specified condition changes.
// The `key` is the prefix of service key.
func (r *Registry) Watch(ctx context.Context, key string) (gsvc.Watcher, error) {
return newWatcher(ctx, r.opts.namespace, key, r.conn)
}

View File

@ -10,15 +10,17 @@ import (
"context"
"errors"
"github.com/go-zookeeper/zk"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gsvc"
"golang.org/x/sync/singleflight"
"path"
"strings"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gsvc"
)
var _ gsvc.Watcher = (*watcher)(nil)
// ErrWatcherStopped is the certain error for watcher closed.
var ErrWatcherStopped = errors.New("watcher stopped")
type watcher struct {
@ -43,6 +45,8 @@ func newWatcher(ctx context.Context, nameSpace, prefix string, conn *zk.Conn) (*
return w, nil
}
// Proceed proceeds watch in blocking way.
// It returns all complete services that watched by `key` if any change.
func (w *watcher) Proceed() ([]gsvc.Service, error) {
select {
case <-w.ctx.Done():
@ -111,6 +115,7 @@ func (w *watcher) getServicesByPrefix() ([]gsvc.Service, error) {
return instances.([]gsvc.Service), nil
}
// Close closes the watcher.
func (w *watcher) Close() error {
w.cancel()
return nil

18
contrib/rpc/grpcx/go.mod Normal file
View File

@ -0,0 +1,18 @@
module github.com/gogf/gf/contrib/rpc/grpcx/v2
go 1.15
require (
github.com/gogf/gf/contrib/registry/file/v2 v2.3.3
github.com/gogf/gf/v2 v2.0.0
go.opentelemetry.io/otel v1.10.0
go.opentelemetry.io/otel/trace v1.10.0
golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1 // indirect
google.golang.org/grpc v1.49.0
google.golang.org/protobuf v1.28.1
)
replace (
github.com/gogf/gf/contrib/registry/file/v2 => ../../registry/file/
github.com/gogf/gf/v2 => ../../../
)

199
contrib/rpc/grpcx/go.sum Normal file
View File

@ -0,0 +1,199 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4=
go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ=
go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0=
go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU=
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E=
go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1 h1:TWZxd/th7FbRSMret2MVQdlI8uT49QEtwZdvJrxjEHU=
golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2 h1:GLw7MR8AfAG2GmGcmVgObFOHXYypgGjnGno25RDwn3Y=
golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -0,0 +1,40 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package grpcx provides grpc service functionalities.
package grpcx
import (
"github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/balancer"
"github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/grpcctx"
"github.com/gogf/gf/contrib/rpc/grpcx/v2/internal/resolver"
)
type (
modCtx = grpcctx.Ctx
modBalancer = balancer.Balancer
modResolver = resolver.Manager
modClient struct{}
modServer struct{}
)
const (
// FreePortAddress marks the server listens using random free port.
FreePortAddress = ":0"
)
const (
defaultServerName = `default`
configNodeNameGrpcServer = `grpc`
)
var (
Ctx = modCtx{} // Ctx is instance of module Context, which manages the context feature.
Balancer = modBalancer{} // Balancer is instance of module Balancer, which manages the load balancer features.
Resolver = modResolver{} // Resolver is instance of module Resolver, which manages the DNS resolving for client.
Client = modClient{} // Client is instance of module Client, which manages the client features.
Server = modServer{} // Server is instance of module Server, which manages the server feature.
)

Some files were not shown because too many files have changed in this diff Show More