mirror of
https://gitee.com/johng/gf
synced 2026-07-04 04:52:48 +08:00
Compare commits
55 Commits
v2.3.0
...
v2.4.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b6798acb5 | |||
| e721124b6c | |||
| b32eb30212 | |||
| 967a39ecbe | |||
| dfb7f5abfb | |||
| 56f5d5125b | |||
| 5083174a92 | |||
| 4a278dfd79 | |||
| 80d57ed8f9 | |||
| b742e222d6 | |||
| ae86f66545 | |||
| 45e4c9e16c | |||
| 8c07f1a42c | |||
| e6c97410ef | |||
| b1a55c7a32 | |||
| 1cd1449085 | |||
| 55690f3738 | |||
| 13f6fb1929 | |||
| e8088a6563 | |||
| e8051bad9a | |||
| d0d41a63a6 | |||
| 853b038a47 | |||
| 34946f6105 | |||
| 15d88c269d | |||
| cbbfd85eeb | |||
| adf90c876f | |||
| b4f76b8448 | |||
| ed858ebd4b | |||
| 272b9c7afd | |||
| 8dc8dd9756 | |||
| a64d1001e2 | |||
| ad737ded3c | |||
| ac6b0c0980 | |||
| b69e0ff9f7 | |||
| 0361f9f7de | |||
| 005668aca8 | |||
| 013f8b216a | |||
| 8ecfa91e5d | |||
| 117fc6eda2 | |||
| d66af122c7 | |||
| a7467945ca | |||
| 81d8aa55cd | |||
| 4a6630138d | |||
| 3adae3a9aa | |||
| 21ebf48072 | |||
| 2b90bcfab6 | |||
| 5f0641f348 | |||
| 38c9cac578 | |||
| 9ba49fa454 | |||
| 39fede66e6 | |||
| d984f1a9d8 | |||
| 28b8efe00c | |||
| 7b0fd6de9b | |||
| c0fa2e3a73 | |||
| 3f6669e2b7 |
26
.github/workflows/gf.yml
vendored
26
.github/workflows/gf.yml
vendored
@ -35,6 +35,15 @@ jobs:
|
|||||||
|
|
||||||
# Service containers to run with `code-test`
|
# Service containers to run with `code-test`
|
||||||
services:
|
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 backend server.
|
||||||
redis:
|
redis:
|
||||||
image : loads/redis:7.0
|
image : loads/redis:7.0
|
||||||
@ -54,7 +63,6 @@ jobs:
|
|||||||
MYSQL_DATABASE : test
|
MYSQL_DATABASE : test
|
||||||
MYSQL_ROOT_PASSWORD: 12345678
|
MYSQL_ROOT_PASSWORD: 12345678
|
||||||
ports:
|
ports:
|
||||||
# Maps tcp port 3306 on service container to the host
|
|
||||||
- 3306:3306
|
- 3306:3306
|
||||||
|
|
||||||
# PostgreSQL backend server.
|
# PostgreSQL backend server.
|
||||||
@ -103,6 +111,7 @@ jobs:
|
|||||||
- 9001:9001
|
- 9001:9001
|
||||||
|
|
||||||
# Polaris backend server.
|
# 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:
|
polaris:
|
||||||
image: loads/polaris-server-standalone:1.11.2
|
image: loads/polaris-server-standalone:1.11.2
|
||||||
ports:
|
ports:
|
||||||
@ -122,6 +131,7 @@ jobs:
|
|||||||
- 1521:1521
|
- 1521:1521
|
||||||
|
|
||||||
# dm8 server
|
# dm8 server
|
||||||
|
# docker run -d --name dm -p 5236:5236 loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4
|
||||||
dm-server:
|
dm-server:
|
||||||
image: loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4
|
image: loads/dm:v8.1.2.128_ent_x86_64_ctm_pack4
|
||||||
ports:
|
ports:
|
||||||
@ -163,18 +173,8 @@ jobs:
|
|||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
|
cache: true
|
||||||
- name: Setup Golang caches
|
cache-dependency-path: '**/go.sum'
|
||||||
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 }}-
|
|
||||||
|
|
||||||
- name: Before Script
|
- name: Before Script
|
||||||
run: bash .github/workflows/before_script.sh
|
run: bash .github/workflows/before_script.sh
|
||||||
|
|||||||
2
.github/workflows/issue-check-inactive.yml
vendored
2
.github/workflows/issue-check-inactive.yml
vendored
@ -25,4 +25,4 @@ jobs:
|
|||||||
inactive-label: 'inactive'
|
inactive-label: 'inactive'
|
||||||
inactive-day: 7
|
inactive-day: 7
|
||||||
issue-state: open
|
issue-state: open
|
||||||
exclude-labels: 'bug,$exclude-empty'
|
exclude-labels: 'bug,planned,$exclude-empty'
|
||||||
19
.github/workflows/issue-translator.yml
vendored
Normal file
19
.github/workflows/issue-translator.yml
vendored
Normal 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. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -7,11 +7,8 @@
|
|||||||
.settings/
|
.settings/
|
||||||
.vscode/
|
.vscode/
|
||||||
vendor/
|
vendor/
|
||||||
composer.lock
|
|
||||||
gitpush.sh
|
|
||||||
pkg/
|
pkg/
|
||||||
bin/
|
bin/
|
||||||
cbuild
|
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
.test/
|
.test/
|
||||||
cmd/gf/main
|
cmd/gf/main
|
||||||
|
|||||||
37
README.MD
37
README.MD
@ -1,18 +1,20 @@
|
|||||||
# GoFrame
|
# GoFrame
|
||||||
|
|
||||||
<div align=center>
|
<div align=center>
|
||||||
<img src="https://goframe.org/statics/image/gf-head-large.png" width="100"/>
|
<img src="https://goframe.org/statics/image/gf-head-large.png" width="100"/>
|
||||||
|
|
||||||
[](https://godoc.org/github.com/gogf/gf)
|
[](https://godoc.org/github.com/gogf/gf)
|
||||||
[](https://github.com/gogf/gf/actions/workflows/gf.yml)
|
[](https://github.com/gogf/gf/actions/workflows/gf.yml)
|
||||||
[](https://goreportcard.com/report/github.com/gogf/gf)
|
[](https://goreportcard.com/report/github.com/gogf/gf/v2)
|
||||||
[](https://codecov.io/gh/gogf/gf/branch/master)
|
[](https://codecov.io/gh/gogf/gf)
|
||||||
[](https://github.com/gogf/gf)
|
[](https://github.com/gogf/gf)
|
||||||
[](https://github.com/gogf/gf)
|
[](https://github.com/gogf/gf)
|
||||||
</div>
|
</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
|
# Features
|
||||||
|
|
||||||
- modular, loosely coupled design
|
- modular, loosely coupled design
|
||||||
- rich components, out-of-the-box
|
- rich components, out-of-the-box
|
||||||
- automatic codes generating for efficiency
|
- automatic codes generating for efficiency
|
||||||
@ -27,34 +29,37 @@
|
|||||||
- much, much more...ready to explore?
|
- much, much more...ready to explore?
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
Enter your repo. directory and execute following command:
|
Enter your repo. directory and execute following command:
|
||||||
|
|
||||||
## primary module
|
## primary module
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get -u -v github.com/gogf/gf/v2
|
go get -u -v github.com/gogf/gf/v2
|
||||||
```
|
```
|
||||||
|
|
||||||
## cli tool
|
## cli tool
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go install github.com/gogf/gf/cmd/gf/v2
|
go install github.com/gogf/gf/cmd/gf/v2
|
||||||
```
|
```
|
||||||
|
|
||||||
# Limitation
|
# Limitation
|
||||||
|
|
||||||
```
|
```
|
||||||
golang version >= 1.15
|
golang version >= 1.15
|
||||||
```
|
```
|
||||||
|
|
||||||
# Architecture
|
# Architecture
|
||||||
|
|
||||||
<div align=center>
|
<div align=center>
|
||||||
<img src="https://goframe.org/download/attachments/1114119/arch.png"/>
|
<img src="https://goframe.org/download/attachments/1114119/arch.png"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
* Chinese Official Site(中文官网): [https://goframe.org](https://goframe.org/display/gf)
|
- 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)
|
- GoDoc API: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
|
||||||
|
|
||||||
|
|
||||||
# License
|
# 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).
|
> 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
|
# Contributors
|
||||||
|
|
||||||
This project exists thanks to all the people who contribute. [[Contributors](https://github.com/gogf/gf/graphs/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>
|
<a href="https://github.com/gogf/gf/graphs/contributors"><img src="https://contributors-img.web.app/image?repo=gogf/gf" /></a>
|
||||||
|
|
||||||
|
|
||||||
# Donators
|
# Donators
|
||||||
|
|
||||||
If you love `GoFrame`, why not [buy developer a cup of coffee](https://goframe.org/pages/viewpage.action?pageId=1115633)?
|
If you love `GoFrame`, why not [buy developer a cup of coffee](https://goframe.org/pages/viewpage.action?pageId=1115633)?
|
||||||
|
|
||||||
# Sponsors
|
# Sponsors
|
||||||
|
|
||||||
We appreciate any kind of sponsorship for `GoFrame` development. If you've got some interesting, please contact WeChat `389961817` / Email `john@goframe.org`.
|
We appreciate any kind of sponsorship for `GoFrame` development. If you've got some interesting, please contact WeChat `389961817` / Email `john@goframe.org`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Thanks
|
# 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.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>
|
<a href="https://www.atlassian.com/?from=GoFrame"><img src="https://goframe.org/download/attachments/1114119/atlassian.jpg" height="120" alt="Atlassian"/></a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,54 +2,67 @@
|
|||||||
|
|
||||||
`gf` is a powerful CLI tool for building [GoFrame](https://goframe.org) application with convenience.
|
`gf` is a powerful CLI tool for building [GoFrame](https://goframe.org) application with convenience.
|
||||||
|
|
||||||
|
|
||||||
## 1. Install
|
## 1. Install
|
||||||
|
|
||||||
## 1) PreCompiled Binary
|
## 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`
|
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
|
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`.
|
> 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`
|
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
|
## 2) Manually Install
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/gogf/gf && cd gf/cmd/gf && go install
|
git clone https://github.com/gogf/gf && cd gf/cmd/gf && go install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## 2. Commands
|
## 2. Commands
|
||||||
|
|
||||||
```html
|
```html
|
||||||
$ gf
|
$ gf
|
||||||
USAGE
|
USAGE
|
||||||
gf COMMAND [OPTION]
|
gf COMMAND [OPTION]
|
||||||
|
|
||||||
COMMAND
|
COMMAND
|
||||||
env show current Golang environment variables
|
env show current Golang environment variables
|
||||||
run running go codes with hot-compiled-like feature
|
run running go codes with hot-compiled-like feature
|
||||||
gen automatically generate go files for dao/do/entity/pb/pbentity
|
gen automatically generate go files for dao/do/entity/pb/pbentity
|
||||||
tpl template parsing and building commands
|
tpl template parsing and building commands
|
||||||
init create and initialize an empty GoFrame project
|
init create and initialize an empty GoFrame project
|
||||||
pack packing any file/directory to a resource file, or a go file
|
pack packing any file/directory to a resource file, or a go file
|
||||||
build cross-building go project for lots of platforms
|
build cross-building go project for lots of platforms
|
||||||
docker build docker image for current GoFrame project
|
docker build docker image for current GoFrame project
|
||||||
install install gf binary to system (might need root/admin permission)
|
install install gf binary to system (might need root/admin permission)
|
||||||
version show version information of current binary
|
version show version information of current binary
|
||||||
|
|
||||||
OPTION
|
OPTION
|
||||||
-y, --yes all yes for all command without prompt ask
|
-y, --yes all yes for all command without prompt ask
|
||||||
-v, --version show version information of current binary
|
-v, --version show version information of current binary
|
||||||
-d, --debug show internal detailed debugging information
|
-d, --debug show internal detailed debugging information
|
||||||
-h, --help more information about this command
|
-h, --help more information about this command
|
||||||
|
|
||||||
ADDITIONAL
|
ADDITIONAL
|
||||||
Use "gf COMMAND -h" for details about a command.
|
Use "gf COMMAND -h" for details about a command.
|
||||||
@ -60,10 +73,3 @@ ADDITIONAL
|
|||||||
### 1). Command `gf run` returns `pipe: too many open files`
|
### 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`.
|
Please use `ulimit -n 65535` to enlarge your system configuration for max open files for current terminal shell session, and then `gf run`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -39,9 +39,9 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v1.7.0 // indirect
|
go.opentelemetry.io/otel/trace v1.7.0 // indirect
|
||||||
golang.org/x/crypto v0.1.0 // indirect
|
golang.org/x/crypto v0.1.0 // indirect
|
||||||
golang.org/x/mod v0.6.0 // indirect
|
golang.org/x/mod v0.6.0 // indirect
|
||||||
golang.org/x/net v0.1.0 // indirect
|
golang.org/x/net v0.7.0 // indirect
|
||||||
golang.org/x/sys v0.1.0 // indirect
|
golang.org/x/sys v0.5.0 // indirect
|
||||||
golang.org/x/text v0.4.0 // indirect
|
golang.org/x/text v0.7.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/libc v1.16.8 // indirect
|
modernc.org/libc v1.16.8 // indirect
|
||||||
modernc.org/mathutil v1.4.1 // indirect
|
modernc.org/mathutil v1.4.1 // indirect
|
||||||
|
|||||||
@ -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-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-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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
|
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
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-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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-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-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.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.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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/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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/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.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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
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-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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/os/gcmd"
|
"github.com/gogf/gf/v2/os/gcmd"
|
||||||
"github.com/gogf/gf/v2/util/gtag"
|
"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{})
|
_, err = Version.Index(ctx, cVersionInput{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
answer := "n"
|
||||||
// No argument or option, do installation checks.
|
// 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.")
|
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]: ")
|
answer = gcmd.Scanf("do you want to install gf(%s) binary to your system? [y/n]: ", gf.VERSION)
|
||||||
if strings.EqualFold(s, "y") {
|
} else if !data.IsSelf {
|
||||||
if err = service.Install.Run(ctx); err != nil {
|
mlog.Print("hi, you have installed gf cli.")
|
||||||
return
|
answer = gcmd.Scanf("do you want to install gf(%s) binary to your system? [y/n]: ", gf.VERSION)
|
||||||
}
|
}
|
||||||
gcmd.Scan("press `Enter` to exit...")
|
if strings.EqualFold(answer, "y") {
|
||||||
|
if err = service.Install.Run(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
gcmd.Scan("press `Enter` to exit...")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
// Print help content.
|
// Print help content.
|
||||||
gcmd.CommandFromCtx(ctx).Print()
|
gcmd.CommandFromCtx(ctx).Print()
|
||||||
|
|||||||
@ -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"`
|
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"`
|
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"`
|
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{}
|
type cBuildOutput struct{}
|
||||||
|
|
||||||
func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, err error) {
|
func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, err error) {
|
||||||
// print used go env
|
// print used go env
|
||||||
_, _ = Env.Index(ctx, cEnvInput{})
|
if in.DumpENV {
|
||||||
|
_, _ = Env.Index(ctx, cEnvInput{})
|
||||||
|
}
|
||||||
|
|
||||||
mlog.SetHeaderPrint(true)
|
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) {
|
if len(customSystems) > 0 && customSystems[0] != "all" && !gstr.InArray(customSystems, system) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for arch, _ := range item {
|
for arch := range item {
|
||||||
if len(customArches) > 0 && customArches[0] != "all" && !gstr.InArray(customArches, arch) {
|
if len(customArches) > 0 && customArches[0] != "all" && !gstr.InArray(customArches, arch) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,6 +33,7 @@ gf docker main.go
|
|||||||
gf docker main.go -t hub.docker.com/john/image:tag
|
gf docker main.go -t hub.docker.com/john/image:tag
|
||||||
gf docker main.go -t hub.docker.com/john/image:tag
|
gf docker main.go -t hub.docker.com/john/image:tag
|
||||||
gf docker main.go -p -t hub.docker.com/john/image:tag
|
gf docker main.go -p -t hub.docker.com/john/image:tag
|
||||||
|
gf docker main.go -p -tp ["hub.docker.com/john","hub.docker.com/smith"] -tn image:tag
|
||||||
`
|
`
|
||||||
cDockerDc = `
|
cDockerDc = `
|
||||||
The "docker" command builds the GF project to a docker images.
|
The "docker" command builds the GF project to a docker images.
|
||||||
@ -45,6 +46,7 @@ You should have docker installed, and there must be a Dockerfile in the root of
|
|||||||
cDockerFileBrief = `file path of the Dockerfile. it's "manifest/docker/Dockerfile" in default`
|
cDockerFileBrief = `file path of the Dockerfile. it's "manifest/docker/Dockerfile" in default`
|
||||||
cDockerShellBrief = `path of the shell file which is executed before docker build`
|
cDockerShellBrief = `path of the shell file which is executed before docker build`
|
||||||
cDockerPushBrief = `auto push the docker image to docker registry if "-t" option passed`
|
cDockerPushBrief = `auto push the docker image to docker registry if "-t" option passed`
|
||||||
|
cDockerTagBrief = `full tag for this docker, pattern like "xxx.xxx.xxx/image:tag"`
|
||||||
cDockerTagNameBrief = `tag name for this docker, pattern like "image:tag". this option is required with TagPrefixes`
|
cDockerTagNameBrief = `tag name for this docker, pattern like "image:tag". this option is required with TagPrefixes`
|
||||||
cDockerTagPrefixesBrief = `tag prefixes for this docker, which are used for docker push. this option is required with TagName`
|
cDockerTagPrefixesBrief = `tag prefixes for this docker, which are used for docker push. this option is required with TagName`
|
||||||
cDockerExtraBrief = `extra build options passed to "docker image"`
|
cDockerExtraBrief = `extra build options passed to "docker image"`
|
||||||
@ -61,6 +63,7 @@ func init() {
|
|||||||
`cDockerShellBrief`: cDockerShellBrief,
|
`cDockerShellBrief`: cDockerShellBrief,
|
||||||
`cDockerBuildBrief`: cDockerBuildBrief,
|
`cDockerBuildBrief`: cDockerBuildBrief,
|
||||||
`cDockerPushBrief`: cDockerPushBrief,
|
`cDockerPushBrief`: cDockerPushBrief,
|
||||||
|
`cDockerTagBrief`: cDockerTagBrief,
|
||||||
`cDockerTagNameBrief`: cDockerTagNameBrief,
|
`cDockerTagNameBrief`: cDockerTagNameBrief,
|
||||||
`cDockerTagPrefixesBrief`: cDockerTagPrefixesBrief,
|
`cDockerTagPrefixesBrief`: cDockerTagPrefixesBrief,
|
||||||
`cDockerExtraBrief`: cDockerExtraBrief,
|
`cDockerExtraBrief`: cDockerExtraBrief,
|
||||||
@ -73,6 +76,7 @@ type cDockerInput struct {
|
|||||||
File string `name:"file" short:"f" brief:"{cDockerFileBrief}" d:"manifest/docker/Dockerfile"`
|
File string `name:"file" short:"f" brief:"{cDockerFileBrief}" d:"manifest/docker/Dockerfile"`
|
||||||
Shell string `name:"shell" short:"s" brief:"{cDockerShellBrief}" d:"manifest/docker/docker.sh"`
|
Shell string `name:"shell" short:"s" brief:"{cDockerShellBrief}" d:"manifest/docker/docker.sh"`
|
||||||
Build string `name:"build" short:"b" brief:"{cDockerBuildBrief}" d:"-a amd64 -s linux"`
|
Build string `name:"build" short:"b" brief:"{cDockerBuildBrief}" d:"-a amd64 -s linux"`
|
||||||
|
Tag string `name:"tag" short:"t" brief:"{cDockerTagBrief}"`
|
||||||
TagName string `name:"tagName" short:"tn" brief:"{cDockerTagNameBrief}" v:"required-with:TagPrefixes"`
|
TagName string `name:"tagName" short:"tn" brief:"{cDockerTagNameBrief}" v:"required-with:TagPrefixes"`
|
||||||
TagPrefixes []string `name:"tagPrefixes" short:"tp" brief:"{cDockerTagPrefixesBrief}" v:"required-with:TagName"`
|
TagPrefixes []string `name:"tagPrefixes" short:"tp" brief:"{cDockerTagPrefixesBrief}" v:"required-with:TagName"`
|
||||||
Push bool `name:"push" short:"p" brief:"{cDockerPushBrief}" orphan:"true"`
|
Push bool `name:"push" short:"p" brief:"{cDockerPushBrief}" orphan:"true"`
|
||||||
@ -114,7 +118,7 @@ func (c cDocker) Index(ctx context.Context, in cDockerInput) (out *cDockerOutput
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(dockerTags) == 0 {
|
if len(dockerTags) == 0 {
|
||||||
dockerTags = []string{""}
|
dockerTags = []string{in.Tag}
|
||||||
}
|
}
|
||||||
for i, dockerTag := range dockerTags {
|
for i, dockerTag := range dockerTags {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/gogf/gf/v2/os/gproc"
|
||||||
|
|
||||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
@ -19,8 +20,9 @@ type cFix struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type cFixInput struct {
|
type cFixInput struct {
|
||||||
g.Meta `name:"fix"`
|
g.Meta `name:"fix"`
|
||||||
Path string `name:"path" brief:"directory path, it uses current working directory in default"`
|
Path string `name:"path" short:"p" brief:"directory path, it uses current working directory in default"`
|
||||||
|
Version string `name:"version" short:"v" brief:"custom specified version to fix, leave it empty to auto detect"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type cFixOutput struct{}
|
type cFixOutput struct{}
|
||||||
@ -31,38 +33,45 @@ type cFixItem struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c cFix) Index(ctx context.Context, in cFixInput) (out *cFixOutput, err error) {
|
func (c cFix) Index(ctx context.Context, in cFixInput) (out *cFixOutput, err error) {
|
||||||
mlog.Print(`start auto fixing...`)
|
|
||||||
defer mlog.Print(`done!`)
|
|
||||||
if in.Path == "" {
|
if in.Path == "" {
|
||||||
in.Path = gfile.Pwd()
|
in.Path = gfile.Pwd()
|
||||||
}
|
}
|
||||||
|
if in.Version == "" {
|
||||||
|
in.Version, err = c.autoDetectVersion(in)
|
||||||
|
if err != nil {
|
||||||
|
mlog.Fatal(err)
|
||||||
|
}
|
||||||
|
if in.Version == "" {
|
||||||
|
mlog.Print(`no GoFrame usage found, exit fixing`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mlog.Debugf(`current GoFrame version auto detect "%s"`, in.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !gproc.IsChild() {
|
||||||
|
mlog.Printf(`start auto fixing directory path "%s"...`, in.Path)
|
||||||
|
defer mlog.Print(`done!`)
|
||||||
|
}
|
||||||
|
|
||||||
err = c.doFix(in)
|
err = c.doFix(in)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c cFix) doFix(in cFixInput) (err error) {
|
func (c cFix) doFix(in cFixInput) (err error) {
|
||||||
version, err := c.getVersion(in)
|
|
||||||
if err != nil {
|
|
||||||
mlog.Fatal(err)
|
|
||||||
}
|
|
||||||
if version == "" {
|
|
||||||
mlog.Print(`no GoFrame usage found, exit fixing`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mlog.Debugf(`current GoFrame version found "%s"`, version)
|
|
||||||
|
|
||||||
var items = []cFixItem{
|
var items = []cFixItem{
|
||||||
{Version: "v2.3", Func: c.doFixV23},
|
{Version: "v2.3", Func: c.doFixV23},
|
||||||
}
|
}
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if gstr.CompareVersionGo(version, item.Version) < 0 {
|
if gstr.CompareVersionGo(in.Version, item.Version) < 0 {
|
||||||
mlog.Debugf(
|
mlog.Debugf(
|
||||||
`current GoFrame version "%s" is lesser than "%s", nothing to do`,
|
`current GoFrame or contrib package version "%s" is lesser than "%s", nothing to do`,
|
||||||
version, item.Version,
|
in.Version, item.Version,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err = item.Func(version); err != nil {
|
if err = item.Func(in.Version); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,7 +96,7 @@ func (c cFix) doFixV23(version string) error {
|
|||||||
return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true)
|
return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c cFix) getVersion(in cFixInput) (string, error) {
|
func (c cFix) autoDetectVersion(in cFixInput) (string, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
path = gfile.Join(in.Path, "go.mod")
|
path = gfile.Join(in.Path, "go.mod")
|
||||||
|
|||||||
@ -1,79 +1,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import "github.com/gogf/gf/cmd/gf/v2/internal/cmd/genpb"
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
cGenPb struct{}
|
cGenPb = genpb.CGenPb
|
||||||
cGenPbInput struct {
|
|
||||||
g.Meta `name:"pb" brief:"parse proto files and generate protobuf go files"`
|
|
||||||
}
|
|
||||||
cGenPbOutput struct{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,411 +1,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import "github.com/gogf/gf/cmd/gf/v2/internal/cmd/genpbentity"
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
cGenPbEntity struct{}
|
cGenPbEntity = genpbentity.CGenPbEntity
|
||||||
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.
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/container/gtype"
|
"github.com/gogf/gf/v2/container/gtype"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
@ -154,7 +155,7 @@ func (app *cRunApp) Run(ctx context.Context) {
|
|||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
// Special handling for windows platform.
|
// Special handling for windows platform.
|
||||||
// DO NOT USE "cmd /c" command.
|
// DO NOT USE "cmd /c" command.
|
||||||
process = gproc.NewProcess(runCommand, nil)
|
process = gproc.NewProcess(outputPath, strings.Fields(app.Args))
|
||||||
} else {
|
} else {
|
||||||
process = gproc.NewProcessCmd(runCommand, nil)
|
process = gproc.NewProcessCmd(runCommand, nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,9 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
|
||||||
|
"github.com/gogf/gf/v2/container/gset"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
@ -26,7 +29,7 @@ const (
|
|||||||
gf up
|
gf up
|
||||||
gf up -a
|
gf up -a
|
||||||
gf up -c
|
gf up -c
|
||||||
gf up -f -c
|
gf up -cf
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,8 +42,8 @@ func init() {
|
|||||||
type cUpInput struct {
|
type cUpInput struct {
|
||||||
g.Meta `name:"up" config:"gfcli.up"`
|
g.Meta `name:"up" config:"gfcli.up"`
|
||||||
All bool `name:"all" short:"a" brief:"upgrade both version and cli, auto fix codes" orphan:"true"`
|
All bool `name:"all" short:"a" brief:"upgrade both version and cli, auto fix codes" orphan:"true"`
|
||||||
Fix bool `name:"fix" short:"f" brief:"auto fix codes" orphan:"true"`
|
Cli bool `name:"cli" short:"c" brief:"also upgrade CLI tool" orphan:"true"`
|
||||||
Cli bool `name:"cli" short:"c" brief:"also upgrade CLI tool (not supported yet)" orphan:"true"`
|
Fix bool `name:"fix" short:"f" brief:"auto fix codes(it only make sense if cli is to be upgraded)" orphan:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type cUpOutput struct{}
|
type cUpOutput struct{}
|
||||||
@ -48,42 +51,72 @@ type cUpOutput struct{}
|
|||||||
func (c cUp) Index(ctx context.Context, in cUpInput) (out *cUpOutput, err error) {
|
func (c cUp) Index(ctx context.Context, in cUpInput) (out *cUpOutput, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
mlog.Print(`done!`)
|
mlog.Print()
|
||||||
|
mlog.Print(`👏congratulations! you've upgraded to the latest version of GoFrame! enjoy it!👏`)
|
||||||
|
mlog.Print()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var doUpgradeVersionOut *doUpgradeVersionOutput
|
||||||
if in.All {
|
if in.All {
|
||||||
in.Cli = true
|
in.Cli = true
|
||||||
in.Fix = true
|
in.Fix = true
|
||||||
}
|
}
|
||||||
if err = c.doUpgradeVersion(ctx, in); err != nil {
|
if doUpgradeVersionOut, err = c.doUpgradeVersion(ctx, in); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
//if in.Cli {
|
|
||||||
// if err = c.doUpgradeCLI(ctx); err != nil {
|
if in.Cli {
|
||||||
// return nil, err
|
if err = c.doUpgradeCLI(ctx); err != nil {
|
||||||
// }
|
return nil, err
|
||||||
//}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if in.Cli && in.Fix {
|
||||||
|
if doUpgradeVersionOut != nil && len(doUpgradeVersionOut.Items) > 0 {
|
||||||
|
upgradedPathSet := gset.NewStrSet()
|
||||||
|
for _, item := range doUpgradeVersionOut.Items {
|
||||||
|
if !upgradedPathSet.AddIfNotExist(item.DirPath) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err = c.doAutoFixing(ctx, item.DirPath, item.Version); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (err error) {
|
type doUpgradeVersionOutput struct {
|
||||||
mlog.Print(`start upgrading version...`)
|
Items []doUpgradeVersionOutputItem
|
||||||
|
}
|
||||||
|
|
||||||
|
type doUpgradeVersionOutputItem struct {
|
||||||
|
DirPath string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (out *doUpgradeVersionOutput, err error) {
|
||||||
|
mlog.Print(`start upgrading version...`)
|
||||||
|
out = &doUpgradeVersionOutput{
|
||||||
|
Items: make([]doUpgradeVersionOutputItem, 0),
|
||||||
|
}
|
||||||
type Package struct {
|
type Package struct {
|
||||||
Name string
|
Name string
|
||||||
Version string
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dir = gfile.Pwd()
|
temp string
|
||||||
temp string
|
dirPath = gfile.Pwd()
|
||||||
path = gfile.Join(dir, "go.mod")
|
goModPath = gfile.Join(dirPath, "go.mod")
|
||||||
)
|
)
|
||||||
|
// It recursively upgrades the go.mod from sub folder to its parent folders.
|
||||||
for {
|
for {
|
||||||
if gfile.Exists(path) {
|
if gfile.Exists(goModPath) {
|
||||||
var packages []Package
|
var packages []Package
|
||||||
err = gfile.ReadLines(path, func(line string) error {
|
err = gfile.ReadLines(goModPath, func(line string) error {
|
||||||
line = gstr.Trim(line)
|
line = gstr.Trim(line)
|
||||||
if gstr.HasPrefix(line, gfPackage) {
|
if gstr.HasPrefix(line, gfPackage) {
|
||||||
array := gstr.SplitAndTrim(line, " ")
|
array := gstr.SplitAndTrim(line, " ")
|
||||||
@ -99,38 +132,76 @@ func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (err error) {
|
|||||||
}
|
}
|
||||||
for _, pkg := range packages {
|
for _, pkg := range packages {
|
||||||
mlog.Printf(`upgrading "%s" from "%s" to "latest"`, pkg.Name, pkg.Version)
|
mlog.Printf(`upgrading "%s" from "%s" to "latest"`, pkg.Name, pkg.Version)
|
||||||
command := fmt.Sprintf(`go get -u %s@latest`, pkg.Name)
|
// go get -u
|
||||||
|
command := fmt.Sprintf(`cd %s && go get -u %s@latest`, dirPath, pkg.Name)
|
||||||
if err = gproc.ShellRun(ctx, command); err != nil {
|
if err = gproc.ShellRun(ctx, command); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mlog.Print()
|
// go mod tidy
|
||||||
}
|
if err = utils.GoModTidy(ctx, dirPath); err != nil {
|
||||||
if in.Fix {
|
return nil, err
|
||||||
if err = c.doAutoFixing(ctx, dir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
mlog.Print()
|
out.Items = append(out.Items, doUpgradeVersionOutputItem{
|
||||||
|
DirPath: dirPath,
|
||||||
|
Version: pkg.Version,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
temp = gfile.Dir(dir)
|
temp = gfile.Dir(dirPath)
|
||||||
if temp == "" || temp == dir {
|
if temp == "" || temp == dirPath {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dir = temp
|
dirPath = temp
|
||||||
path = gfile.Join(dir, "go.mod")
|
goModPath = gfile.Join(dirPath, "go.mod")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// doUpgradeCLI downloads the new version binary with process.
|
||||||
func (c cUp) doUpgradeCLI(ctx context.Context) (err error) {
|
func (c cUp) doUpgradeCLI(ctx context.Context) (err error) {
|
||||||
mlog.Print(`start upgrading cli...`)
|
mlog.Print(`start upgrading cli...`)
|
||||||
|
var (
|
||||||
|
downloadUrl = fmt.Sprintf(
|
||||||
|
`https://github.com/gogf/gf/releases/latest/download/gf_%s_%s`,
|
||||||
|
runtime.GOOS, runtime.GOARCH,
|
||||||
|
)
|
||||||
|
localSaveFilePath = gfile.SelfPath() + "~"
|
||||||
|
)
|
||||||
|
mlog.Printf(`start downloading "%s" to "%s", it may take some time`, downloadUrl, localSaveFilePath)
|
||||||
|
err = utils.HTTPDownloadFileWithPercent(downloadUrl, localSaveFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
mlog.Printf(`new version cli binary is successfully installed to "%s"`, gfile.SelfPath())
|
||||||
|
mlog.Printf(`remove temporary buffer file "%s"`, localSaveFilePath)
|
||||||
|
_ = gfile.Remove(localSaveFilePath)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// It fails if file not exist or its size is less than 1MB.
|
||||||
|
if !gfile.Exists(localSaveFilePath) || gfile.Size(localSaveFilePath) < 1024*1024 {
|
||||||
|
mlog.Fatalf(`download "%s" to "%s" failed`, downloadUrl, localSaveFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It replaces self binary with new version cli binary.
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
if err := gfile.Rename(localSaveFilePath, gfile.SelfPath()); err != nil {
|
||||||
|
mlog.Fatalf(`install failed: %s`, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
if err := gfile.PutBytes(gfile.SelfPath(), gfile.GetBytes(localSaveFilePath)); err != nil {
|
||||||
|
mlog.Fatalf(`install failed: %s`, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c cUp) doAutoFixing(ctx context.Context, dirPath string) (err error) {
|
func (c cUp) doAutoFixing(ctx context.Context, dirPath string, version string) (err error) {
|
||||||
mlog.Printf(`auto fixing path "%s"...`, dirPath)
|
mlog.Printf(`auto fixing directory path "%s" from version "%s" ...`, dirPath, version)
|
||||||
err = cFix{}.doFix(cFixInput{
|
command := fmt.Sprintf(`gf fix -p %s`, dirPath)
|
||||||
Path: dirPath,
|
_ = gproc.ShellRun(ctx, command)
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -216,6 +216,7 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
|
|||||||
}
|
}
|
||||||
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
|
removePrefixArray := gstr.SplitAndTrim(in.RemovePrefix, ",")
|
||||||
if in.ImportPrefix == "" {
|
if in.ImportPrefix == "" {
|
||||||
|
mlog.Debug(`import prefix is empty, trying calculating the import package path using go.mod`)
|
||||||
if !gfile.Exists("go.mod") {
|
if !gfile.Exists("go.mod") {
|
||||||
mlog.Fatal("go.mod does not exist in current working directory")
|
mlog.Fatal("go.mod does not exist in current working directory")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,13 +9,13 @@ import (
|
|||||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
|
"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)
|
files, err := gfile.ScanDirFile(dirPath, "*.go", true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mlog.Fatal(err)
|
mlog.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if utils.IsFileDoNotEdit(file) {
|
if force || utils.IsFileDoNotEdit(file) {
|
||||||
if err = gfile.Remove(file); err != nil {
|
if err = gfile.Remove(file); err != nil {
|
||||||
mlog.Print(err)
|
mlog.Print(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,12 +6,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/olekukonko/tablewriter"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/database/gdb"
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/os/gfile"
|
"github.com/gogf/gf/v2/os/gfile"
|
||||||
"github.com/gogf/gf/v2/text/gregex"
|
"github.com/gogf/gf/v2/text/gregex"
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
"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/consts"
|
||||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
"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")
|
dirPathDaoInternal = gfile.Join(dirPathDao, "internal")
|
||||||
)
|
)
|
||||||
if in.Clear {
|
if in.Clear {
|
||||||
doClear(ctx, dirPathDao)
|
doClear(ctx, dirPathDao, true)
|
||||||
}
|
}
|
||||||
for i := 0; i < len(in.TableNames); i++ {
|
for i := 0; i < len(in.TableNames); i++ {
|
||||||
generateDaoSingle(ctx, generateDaoSingleInput{
|
generateDaoSingle(ctx, generateDaoSingleInput{
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import (
|
|||||||
func generateDo(ctx context.Context, in CGenDaoInternalInput) {
|
func generateDo(ctx context.Context, in CGenDaoInternalInput) {
|
||||||
var dirPathDo = gfile.Join(in.Path, in.DoPath)
|
var dirPathDo = gfile.Join(in.Path, in.DoPath)
|
||||||
if in.Clear {
|
if in.Clear {
|
||||||
doClear(ctx, dirPathDo)
|
doClear(ctx, dirPathDo, false)
|
||||||
}
|
}
|
||||||
in.NoJsonTag = true
|
in.NoJsonTag = true
|
||||||
in.DescriptionTag = false
|
in.DescriptionTag = false
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import (
|
|||||||
func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
|
func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
|
||||||
var dirPathEntity = gfile.Join(in.Path, in.EntityPath)
|
var dirPathEntity = gfile.Join(in.Path, in.EntityPath)
|
||||||
if in.Clear {
|
if in.Clear {
|
||||||
doClear(ctx, dirPathEntity)
|
doClear(ctx, dirPathEntity, false)
|
||||||
}
|
}
|
||||||
// Model content.
|
// Model content.
|
||||||
for i, tableName := range in.TableNames {
|
for i, tableName := range in.TableNames {
|
||||||
|
|||||||
122
cmd/gf/internal/cmd/genpb/genpb.go
Normal file
122
cmd/gf/internal/cmd/genpb/genpb.go
Normal 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
|
||||||
|
}
|
||||||
189
cmd/gf/internal/cmd/genpb/genpb_controller.go
Normal file
189
cmd/gf/internal/cmd/genpb/genpb_controller.go
Normal 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
|
||||||
|
}
|
||||||
107
cmd/gf/internal/cmd/genpb/genpb_tag.go
Normal file
107
cmd/gf/internal/cmd/genpb/genpb_tag.go
Normal 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
|
||||||
|
}
|
||||||
413
cmd/gf/internal/cmd/genpbentity/genpbentity.go
Normal file
413
cmd/gf/internal/cmd/genpbentity/genpbentity.go
Normal 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
|
||||||
|
}
|
||||||
@ -189,6 +189,10 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
|
|||||||
generatedDstFilePathSet.Add(dstFilePath)
|
generatedDstFilePathSet.Add(dstFilePath)
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
fileContent = gfile.GetContents(file)
|
fileContent = gfile.GetContents(file)
|
||||||
|
fileContent, err := gregex.ReplaceString(`/[/|\*](.+)`, "", fileContent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
// Calculate imported packages of source go files.
|
// Calculate imported packages of source go files.
|
||||||
err = c.calculateImportedPackages(fileContent, srcImportedPackages)
|
err = c.calculateImportedPackages(fileContent, srcImportedPackages)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -254,7 +258,7 @@ func (c CGenService) Service(ctx context.Context, in CGenServiceInput) (out *CGe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Replace v1 to v2 for GoFrame.
|
// Replace v1 to v2 for GoFrame.
|
||||||
if err = c.replaceGeneratedServiceContentGFV2(in); err != nil {
|
if err = utils.ReplaceGeneratedContentGFV2(in.DstFolder); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
mlog.Printf(`gofmt go files in "%s"`, in.DstFolder)
|
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!`)
|
mlog.Print(`done!`)
|
||||||
return
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -56,7 +56,7 @@ func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool,
|
|||||||
generatingInterfaceCheck string
|
generatingInterfaceCheck string
|
||||||
)
|
)
|
||||||
// Variable definitions.
|
// 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)
|
generatingInterfaceCheck = fmt.Sprintf(`[^\w\d]+%s.I%s[^\w\d]`, in.DstPackageName, structName)
|
||||||
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
|
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
|
||||||
continue
|
continue
|
||||||
@ -75,7 +75,7 @@ func (c CGenService) generateServiceFile(in generateServiceFilesInput) (ok bool,
|
|||||||
generatedContent += "\n"
|
generatedContent += "\n"
|
||||||
}
|
}
|
||||||
// Variable register function definitions.
|
// 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)
|
generatingInterfaceCheck = fmt.Sprintf(`[^\w\d]+%s.I%s[^\w\d]`, in.DstPackageName, structName)
|
||||||
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
|
if gregex.IsMatchString(generatingInterfaceCheck, generatedContent) {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@ -9,9 +9,9 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package {PackageName};
|
package {PackageName};
|
||||||
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
option go_package = "{GoPackage}";
|
||||||
|
|
||||||
{OptionContent}
|
{OptionContent}
|
||||||
|
{Imports}
|
||||||
|
|
||||||
{EntityMessage}
|
{EntityMessage}
|
||||||
`
|
`
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -29,6 +29,7 @@ type serviceInstallAvailablePath struct {
|
|||||||
filePath string
|
filePath string
|
||||||
writable bool
|
writable bool
|
||||||
installed bool
|
installed bool
|
||||||
|
IsSelf bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s serviceInstall) Run(ctx context.Context) (err error) {
|
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.
|
// IsInstalled checks and returns whether the binary is installed.
|
||||||
func (s serviceInstall) IsInstalled() bool {
|
func (s serviceInstall) IsInstalled() (*serviceInstallAvailablePath, bool) {
|
||||||
paths := s.getAvailablePaths()
|
paths := s.getAvailablePaths()
|
||||||
for _, aPath := range paths {
|
for _, aPath := range paths {
|
||||||
if aPath.installed {
|
if aPath.installed {
|
||||||
return true
|
return &aPath, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// getGoPathBinFilePath retrieves ad returns the GOPATH/bin path for binary.
|
// 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)
|
filePath = gfile.Join(dirPath, binaryFileName)
|
||||||
writable = gfile.IsWritable(dirPath)
|
writable = gfile.IsWritable(dirPath)
|
||||||
installed = gfile.Exists(filePath)
|
installed = gfile.Exists(filePath)
|
||||||
|
self = gfile.SelfPath() == filePath
|
||||||
)
|
)
|
||||||
if !writable && !installed {
|
if !writable && !installed {
|
||||||
return folderPaths
|
return folderPaths
|
||||||
@ -225,5 +227,6 @@ func (s serviceInstall) checkAndAppendToAvailablePath(folderPaths []serviceInsta
|
|||||||
writable: writable,
|
writable: writable,
|
||||||
filePath: filePath,
|
filePath: filePath,
|
||||||
installed: installed,
|
installed: installed,
|
||||||
|
IsSelf: self,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gogf/gf/v2/os/gfile"
|
"context"
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
"fmt"
|
||||||
"golang.org/x/tools/imports"
|
"golang.org/x/tools/imports"
|
||||||
|
|
||||||
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
|
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
|
||||||
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GoFmt formats the source file and adds or removes import statements as necessary.
|
// GoFmt formats the source file and adds or removes import statements as necessary.
|
||||||
@ -36,6 +39,13 @@ func GoFmt(path string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GoModTidy executes `go mod tidy` at specified directory `dirPath`.
|
||||||
|
func GoModTidy(ctx context.Context, dirPath string) error {
|
||||||
|
command := fmt.Sprintf(`cd %s && go mod tidy`, dirPath)
|
||||||
|
err := gproc.ShellRun(ctx, command)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// IsFileDoNotEdit checks and returns whether file contains `do not edit` key.
|
// IsFileDoNotEdit checks and returns whether file contains `do not edit` key.
|
||||||
func IsFileDoNotEdit(filePath string) bool {
|
func IsFileDoNotEdit(filePath string) bool {
|
||||||
if !gfile.Exists(filePath) {
|
if !gfile.Exists(filePath) {
|
||||||
@ -43,3 +53,16 @@ func IsFileDoNotEdit(filePath string) bool {
|
|||||||
}
|
}
|
||||||
return gstr.Contains(gfile.GetContents(filePath), consts.DoNotEditKey)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
98
cmd/gf/internal/utility/utils/utils_http_download.go
Normal file
98
cmd/gf/internal/utility/utils/utils_http_download.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPDownloadFileWithPercent downloads target url file to local path with percent process printing.
|
||||||
|
func HTTPDownloadFileWithPercent(url string, localSaveFilePath string) error {
|
||||||
|
start := time.Now()
|
||||||
|
out, err := os.Create(localSaveFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
headResp, err := http.Head(url)
|
||||||
|
if err != nil {
|
||||||
|
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
|
||||||
|
}
|
||||||
|
defer headResp.Body.Close()
|
||||||
|
|
||||||
|
size, err := strconv.Atoi(headResp.Header.Get("Content-Length"))
|
||||||
|
if err != nil {
|
||||||
|
return gerror.Wrap(err, "retrieve Content-Length failed")
|
||||||
|
}
|
||||||
|
doneCh := make(chan int64)
|
||||||
|
|
||||||
|
go doPrintDownloadPercent(doneCh, localSaveFilePath, int64(size))
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
wroteBytesCount, err := io.Copy(out, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return gerror.Wrapf(err, `download "%s" to "%s" failed`, url, localSaveFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
doneCh <- wroteBytesCount
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
if elapsed > time.Minute {
|
||||||
|
mlog.Printf(`download completed in %.0fm`, float64(elapsed)/float64(time.Minute))
|
||||||
|
} else {
|
||||||
|
mlog.Printf(`download completed in %.0fs`, elapsed.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func doPrintDownloadPercent(doneCh chan int64, localSaveFilePath string, total int64) {
|
||||||
|
var (
|
||||||
|
stop = false
|
||||||
|
lastPercentFmt string
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-doneCh:
|
||||||
|
stop = true
|
||||||
|
|
||||||
|
default:
|
||||||
|
file, err := os.Open(localSaveFilePath)
|
||||||
|
if err != nil {
|
||||||
|
mlog.Fatal(err)
|
||||||
|
}
|
||||||
|
fi, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
mlog.Fatal(err)
|
||||||
|
}
|
||||||
|
size := fi.Size()
|
||||||
|
if size == 0 {
|
||||||
|
size = 1
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
percent = float64(size) / float64(total) * 100
|
||||||
|
percentFmt = fmt.Sprintf(`%.0f`, percent) + "%"
|
||||||
|
)
|
||||||
|
if lastPercentFmt != percentFmt {
|
||||||
|
lastPercentFmt = percentFmt
|
||||||
|
mlog.Print(percentFmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -173,28 +173,28 @@ func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertBefore inserts the `value` to the front of `index`.
|
// InsertBefore inserts the `values` to the front of `index`.
|
||||||
func (a *Array) InsertBefore(index int, value interface{}) error {
|
func (a *Array) InsertBefore(index int, values ...interface{}) error {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
if index < 0 || index >= len(a.array) {
|
if index < 0 || index >= len(a.array) {
|
||||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||||
}
|
}
|
||||||
rear := append([]interface{}{}, a.array[index:]...)
|
rear := append([]interface{}{}, a.array[index:]...)
|
||||||
a.array = append(a.array[0:index], value)
|
a.array = append(a.array[0:index], values...)
|
||||||
a.array = append(a.array, rear...)
|
a.array = append(a.array, rear...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertAfter inserts the `value` to the back of `index`.
|
// InsertAfter inserts the `values` to the back of `index`.
|
||||||
func (a *Array) InsertAfter(index int, value interface{}) error {
|
func (a *Array) InsertAfter(index int, values ...interface{}) error {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
if index < 0 || index >= len(a.array) {
|
if index < 0 || index >= len(a.array) {
|
||||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||||
}
|
}
|
||||||
rear := append([]interface{}{}, a.array[index+1:]...)
|
rear := append([]interface{}{}, a.array[index+1:]...)
|
||||||
a.array = append(a.array[0:index+1], value)
|
a.array = append(a.array[0:index+1], values...)
|
||||||
a.array = append(a.array, rear...)
|
a.array = append(a.array, rear...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -168,28 +168,28 @@ func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertBefore inserts the `value` to the front of `index`.
|
// InsertBefore inserts the `values` to the front of `index`.
|
||||||
func (a *IntArray) InsertBefore(index int, value int) error {
|
func (a *IntArray) InsertBefore(index int, values ...int) error {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
if index < 0 || index >= len(a.array) {
|
if index < 0 || index >= len(a.array) {
|
||||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||||
}
|
}
|
||||||
rear := append([]int{}, a.array[index:]...)
|
rear := append([]int{}, a.array[index:]...)
|
||||||
a.array = append(a.array[0:index], value)
|
a.array = append(a.array[0:index], values...)
|
||||||
a.array = append(a.array, rear...)
|
a.array = append(a.array, rear...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertAfter inserts the `value` to the back of `index`.
|
// InsertAfter inserts the `value` to the back of `index`.
|
||||||
func (a *IntArray) InsertAfter(index int, value int) error {
|
func (a *IntArray) InsertAfter(index int, values ...int) error {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
if index < 0 || index >= len(a.array) {
|
if index < 0 || index >= len(a.array) {
|
||||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||||
}
|
}
|
||||||
rear := append([]int{}, a.array[index+1:]...)
|
rear := append([]int{}, a.array[index+1:]...)
|
||||||
a.array = append(a.array[0:index+1], value)
|
a.array = append(a.array[0:index+1], values...)
|
||||||
a.array = append(a.array, rear...)
|
a.array = append(a.array, rear...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -155,28 +155,28 @@ func (a *StrArray) SortFunc(less func(v1, v2 string) bool) *StrArray {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertBefore inserts the `value` to the front of `index`.
|
// InsertBefore inserts the `values` to the front of `index`.
|
||||||
func (a *StrArray) InsertBefore(index int, value string) error {
|
func (a *StrArray) InsertBefore(index int, values ...string) error {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
if index < 0 || index >= len(a.array) {
|
if index < 0 || index >= len(a.array) {
|
||||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||||
}
|
}
|
||||||
rear := append([]string{}, a.array[index:]...)
|
rear := append([]string{}, a.array[index:]...)
|
||||||
a.array = append(a.array[0:index], value)
|
a.array = append(a.array[0:index], values...)
|
||||||
a.array = append(a.array, rear...)
|
a.array = append(a.array, rear...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertAfter inserts the `value` to the back of `index`.
|
// InsertAfter inserts the `values` to the back of `index`.
|
||||||
func (a *StrArray) InsertAfter(index int, value string) error {
|
func (a *StrArray) InsertAfter(index int, values ...string) error {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
if index < 0 || index >= len(a.array) {
|
if index < 0 || index >= len(a.array) {
|
||||||
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
|
||||||
}
|
}
|
||||||
rear := append([]string{}, a.array[index+1:]...)
|
rear := append([]string{}, a.array[index+1:]...)
|
||||||
a.array = append(a.array[0:index+1], value)
|
a.array = append(a.array[0:index+1], values...)
|
||||||
a.array = append(a.array, rear...)
|
a.array = append(a.array, rear...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -207,9 +207,11 @@ func (a *SortedArray) doRemoveWithoutLock(index int) (value interface{}, found b
|
|||||||
// RemoveValue removes an item by value.
|
// RemoveValue removes an item by value.
|
||||||
// It returns true if value is found in the array, or else false if not found.
|
// It returns true if value is found in the array, or else false if not found.
|
||||||
func (a *SortedArray) RemoveValue(value interface{}) bool {
|
func (a *SortedArray) RemoveValue(value interface{}) bool {
|
||||||
if i := a.Search(value); i != -1 {
|
a.mu.Lock()
|
||||||
a.Remove(i)
|
defer a.mu.Unlock()
|
||||||
return true
|
if i, r := a.binSearch(value, false); r == 0 {
|
||||||
|
_, res := a.doRemoveWithoutLock(i)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -193,9 +193,11 @@ func (a *SortedIntArray) doRemoveWithoutLock(index int) (value int, found bool)
|
|||||||
// RemoveValue removes an item by value.
|
// RemoveValue removes an item by value.
|
||||||
// It returns true if value is found in the array, or else false if not found.
|
// It returns true if value is found in the array, or else false if not found.
|
||||||
func (a *SortedIntArray) RemoveValue(value int) bool {
|
func (a *SortedIntArray) RemoveValue(value int) bool {
|
||||||
if i := a.Search(value); i != -1 {
|
a.mu.Lock()
|
||||||
_, found := a.Remove(i)
|
defer a.mu.Unlock()
|
||||||
return found
|
if i, r := a.binSearch(value, false); r == 0 {
|
||||||
|
_, res := a.doRemoveWithoutLock(i)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -179,9 +179,11 @@ func (a *SortedStrArray) doRemoveWithoutLock(index int) (value string, found boo
|
|||||||
// RemoveValue removes an item by value.
|
// RemoveValue removes an item by value.
|
||||||
// It returns true if value is found in the array, or else false if not found.
|
// It returns true if value is found in the array, or else false if not found.
|
||||||
func (a *SortedStrArray) RemoveValue(value string) bool {
|
func (a *SortedStrArray) RemoveValue(value string) bool {
|
||||||
if i := a.Search(value); i != -1 {
|
a.mu.Lock()
|
||||||
a.Remove(i)
|
defer a.mu.Unlock()
|
||||||
return true
|
if i, r := a.binSearch(value, false); r == 0 {
|
||||||
|
_, res := a.doRemoveWithoutLock(i)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -514,3 +514,24 @@ func (m *AnyAnyMap) DeepCopy() interface{} {
|
|||||||
}
|
}
|
||||||
return NewFrom(data, m.mu.IsSafe())
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IntAnyMap implements map[int]interface{} with RWMutex that has switch.
|
||||||
type IntAnyMap struct {
|
type IntAnyMap struct {
|
||||||
mu rwmutex.RWMutex
|
mu rwmutex.RWMutex
|
||||||
data map[int]interface{}
|
data map[int]interface{}
|
||||||
@ -514,3 +515,24 @@ func (m *IntAnyMap) DeepCopy() interface{} {
|
|||||||
}
|
}
|
||||||
return NewIntAnyMapFrom(data, m.mu.IsSafe())
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IntIntMap implements map[int]int with RWMutex that has switch.
|
||||||
type IntIntMap struct {
|
type IntIntMap struct {
|
||||||
mu rwmutex.RWMutex
|
mu rwmutex.RWMutex
|
||||||
data map[int]int
|
data map[int]int
|
||||||
@ -484,3 +485,24 @@ func (m *IntIntMap) DeepCopy() interface{} {
|
|||||||
}
|
}
|
||||||
return NewIntIntMapFrom(data, m.mu.IsSafe())
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IntStrMap implements map[int]string with RWMutex that has switch.
|
||||||
type IntStrMap struct {
|
type IntStrMap struct {
|
||||||
mu rwmutex.RWMutex
|
mu rwmutex.RWMutex
|
||||||
data map[int]string
|
data map[int]string
|
||||||
@ -484,3 +485,24 @@ func (m *IntStrMap) DeepCopy() interface{} {
|
|||||||
}
|
}
|
||||||
return NewIntStrMapFrom(data, m.mu.IsSafe())
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -501,3 +501,24 @@ func (m *StrAnyMap) DeepCopy() interface{} {
|
|||||||
}
|
}
|
||||||
return NewStrAnyMapFrom(data, m.mu.IsSafe())
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// StrIntMap implements map[string]int with RWMutex that has switch.
|
||||||
type StrIntMap struct {
|
type StrIntMap struct {
|
||||||
mu rwmutex.RWMutex
|
mu rwmutex.RWMutex
|
||||||
data map[string]int
|
data map[string]int
|
||||||
@ -488,3 +489,24 @@ func (m *StrIntMap) DeepCopy() interface{} {
|
|||||||
}
|
}
|
||||||
return NewStrIntMapFrom(data, m.mu.IsSafe())
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// StrStrMap implements map[string]string with RWMutex that has switch.
|
||||||
type StrStrMap struct {
|
type StrStrMap struct {
|
||||||
mu rwmutex.RWMutex
|
mu rwmutex.RWMutex
|
||||||
data map[string]string
|
data map[string]string
|
||||||
@ -477,3 +478,24 @@ func (m *StrStrMap) DeepCopy() interface{} {
|
|||||||
}
|
}
|
||||||
return NewStrStrMapFrom(data, m.mu.IsSafe())
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -391,3 +391,18 @@ func Test_AnyAnyMap_DeepCopy(t *testing.T) {
|
|||||||
t.AssertNE(m.Get("k1"), n.Get("k1"))
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -375,3 +375,18 @@ func Test_IntAnyMap_DeepCopy(t *testing.T) {
|
|||||||
t.AssertNE(m.Get(1), n.Get(1))
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -383,3 +383,18 @@ func Test_IntIntMap_DeepCopy(t *testing.T) {
|
|||||||
t.AssertNE(m.Get(1), n.Get(1))
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -447,3 +447,18 @@ func Test_IntStrMap_DeepCopy(t *testing.T) {
|
|||||||
t.AssertNE(m.Get(1), n.Get(1))
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -381,3 +381,18 @@ func Test_StrAnyMap_DeepCopy(t *testing.T) {
|
|||||||
t.AssertNE(m.Get("key1"), n.Get("key1"))
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -389,3 +389,18 @@ func Test_StrIntMap_DeepCopy(t *testing.T) {
|
|||||||
t.AssertNE(m.Get("key1"), n.Get("key1"))
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -388,3 +388,18 @@ func Test_StrStrMap_DeepCopy(t *testing.T) {
|
|||||||
t.AssertNE(m.Get("key1"), n.Get("key1"))
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -85,7 +85,9 @@ func (q *Queue) Pop() interface{} {
|
|||||||
// Notice: It would notify all goroutines return immediately,
|
// Notice: It would notify all goroutines return immediately,
|
||||||
// which are being blocked reading using Pop method.
|
// which are being blocked reading using Pop method.
|
||||||
func (q *Queue) Close() {
|
func (q *Queue) Close() {
|
||||||
q.closed.Set(true)
|
if !q.closed.Cas(false, true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if q.events != nil {
|
if q.events != nil {
|
||||||
close(q.events)
|
close(q.events)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -114,8 +114,8 @@ func TestSet_LockFunc(t *testing.T) {
|
|||||||
t.Assert(s.Size(), 2)
|
t.Assert(s.Size(), 2)
|
||||||
s.RLockFunc(func(m map[interface{}]struct{}) {
|
s.RLockFunc(func(m map[interface{}]struct{}) {
|
||||||
t.Assert(m, map[interface{}]struct{}{
|
t.Assert(m, map[interface{}]struct{}{
|
||||||
3: struct{}{},
|
3: {},
|
||||||
2: struct{}{},
|
2: {},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -94,8 +94,8 @@ func TestIntSet_LockFunc(t *testing.T) {
|
|||||||
t.Assert(s.Size(), 2)
|
t.Assert(s.Size(), 2)
|
||||||
s.RLockFunc(func(m map[int]struct{}) {
|
s.RLockFunc(func(m map[int]struct{}) {
|
||||||
t.Assert(m, map[int]struct{}{
|
t.Assert(m, map[int]struct{}{
|
||||||
3: struct{}{},
|
3: {},
|
||||||
2: struct{}{},
|
2: {},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -105,8 +105,8 @@ func TestStrSet_LockFunc(t *testing.T) {
|
|||||||
t.Assert(s.Size(), 2)
|
t.Assert(s.Size(), 2)
|
||||||
s.RLockFunc(func(m map[string]struct{}) {
|
s.RLockFunc(func(m map[string]struct{}) {
|
||||||
t.Assert(m, map[string]struct{}{
|
t.Assert(m, map[string]struct{}{
|
||||||
"3": struct{}{},
|
"3": {},
|
||||||
"2": struct{}{},
|
"2": {},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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`.
|
It is strongly recommended import your boot package in top of your `main.go`.
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ go 1.15
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gogf/gf/v2 v2.0.0
|
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 => ../../../
|
replace github.com/gogf/gf/v2 => ../../../
|
||||||
|
|||||||
@ -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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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.3.0 h1:KZKX//ow4OPPoS5+s7h07ptprg+2AcNVGrN6WakC9QM=
|
||||||
github.com/polarismesh/polaris-go v1.2.0-beta.3/go.mod h1:HsN0ierETIujHpmnnYJ3qkwQw4QGAECuHvBZTDaw1tI=
|
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 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.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
|
|||||||
@ -10,21 +10,17 @@ package polaris
|
|||||||
import (
|
import (
|
||||||
"context"
|
"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/encoding/gjson"
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/os/gcfg"
|
"github.com/gogf/gf/v2/os/gcfg"
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
"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.
|
// Config is the configuration for polaris.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// The namespace of the configuration.
|
// 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 {
|
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
|
return
|
||||||
}
|
}
|
||||||
// set log dir
|
// set log dir
|
||||||
if gstr.Trim(config.LogDir) == "" {
|
if gstr.Trim(config.LogDir) == "" {
|
||||||
config.LogDir = defaultLogDir
|
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")
|
err = gerror.Wrap(err, "set polaris log dir failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -85,6 +81,11 @@ func New(ctx context.Context, config Config) (adapter gcfg.Adapter, err error) {
|
|||||||
return client, nil
|
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.
|
// Available checks and returns the backend configuration service is available.
|
||||||
// The optional parameter `resource` specifies certain configuration resource.
|
// 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
|
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:
|
// Pattern like:
|
||||||
// "x.y.z" for map item.
|
// "x.y.z" for map item.
|
||||||
// "x.0.y" for slice 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.
|
// 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.
|
// you can implement this function if necessary.
|
||||||
func (c *Client) Data(ctx context.Context) (data map[string]interface{}, err error) {
|
func (c *Client) Data(ctx context.Context) (data map[string]interface{}, err error) {
|
||||||
if c.value.IsNil() {
|
if c.value.IsNil() {
|
||||||
|
|||||||
@ -58,6 +58,7 @@ Note:
|
|||||||
- It does not support `Save/Replace` features.
|
- It does not support `Save/Replace` features.
|
||||||
- It does not support `LastInsertId`.
|
- It does not support `LastInsertId`.
|
||||||
- It supports server version >= `SQL Server2005`
|
- 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
|
## Oracle
|
||||||
```
|
```
|
||||||
|
|||||||
@ -143,11 +143,11 @@ func (d *Driver) TableFields(
|
|||||||
ctx context.Context, table string, schema ...string,
|
ctx context.Context, table string, schema ...string,
|
||||||
) (fields map[string]*gdb.TableField, err error) {
|
) (fields map[string]*gdb.TableField, err error) {
|
||||||
var (
|
var (
|
||||||
result gdb.Result
|
result gdb.Result
|
||||||
link gdb.Link
|
link gdb.Link
|
||||||
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||||
)
|
)
|
||||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
|
|||||||
@ -455,9 +455,9 @@ func TestDriverClickhouse_NilTime(t *testing.T) {
|
|||||||
Col9: uuid.New(),
|
Col9: uuid.New(),
|
||||||
Col7: []interface{}{ // Tuple(String, UInt8, Array(Map(String, String)))
|
Col7: []interface{}{ // Tuple(String, UInt8, Array(Map(String, String)))
|
||||||
"String Value", uint8(5), []map[string]string{
|
"String Value", uint8(5), []map[string]string{
|
||||||
map[string]string{"key": "value"},
|
{"key": "value"},
|
||||||
map[string]string{"key": "value"},
|
{"key": "value"},
|
||||||
map[string]string{"key": "value"},
|
{"key": "value"},
|
||||||
}},
|
}},
|
||||||
Col11: money,
|
Col11: money,
|
||||||
Col12: &strMoney,
|
Col12: &strMoney,
|
||||||
@ -494,9 +494,9 @@ func TestDriverClickhouse_BatchInsert(t *testing.T) {
|
|||||||
"Col6": []string{"Q", "W", "E", "R", "T", "Y"}, // Array(String)
|
"Col6": []string{"Q", "W", "E", "R", "T", "Y"}, // Array(String)
|
||||||
"Col7": []interface{}{ // Tuple(String, UInt8, Array(Map(String, String)))
|
"Col7": []interface{}{ // Tuple(String, UInt8, Array(Map(String, String)))
|
||||||
"String Value", uint8(5), []map[string]string{
|
"String Value", uint8(5), []map[string]string{
|
||||||
map[string]string{"key": "value"},
|
{"key": "value"},
|
||||||
map[string]string{"key": "value"},
|
{"key": "value"},
|
||||||
map[string]string{"key": "value"},
|
{"key": "value"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Col8": gtime.Now(),
|
"Col8": gtime.Now(),
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/text/gregex"
|
"github.com/gogf/gf/v2/text/gregex"
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
"github.com/gogf/gf/v2/text/gstr"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
"github.com/gogf/gf/v2/util/gutil"
|
"github.com/gogf/gf/v2/util/gutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,6 +31,10 @@ type Driver struct {
|
|||||||
*gdb.Core
|
*gdb.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
quoteChar = `"`
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
@ -72,7 +77,10 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
|||||||
// Demo of timezone setting:
|
// Demo of timezone setting:
|
||||||
// &loc=Asia/Shanghai
|
// &loc=Asia/Shanghai
|
||||||
if config.Timezone != "" {
|
if config.Timezone != "" {
|
||||||
source = fmt.Sprintf("%s&loc%s", source, url.QueryEscape(config.Timezone))
|
if strings.Contains(config.Timezone, "/") {
|
||||||
|
config.Timezone = url.QueryEscape(config.Timezone)
|
||||||
|
}
|
||||||
|
source = fmt.Sprintf("%s&loc%s", source, config.Timezone)
|
||||||
}
|
}
|
||||||
if config.Extra != "" {
|
if config.Extra != "" {
|
||||||
source = fmt.Sprintf("%s&%s", source, config.Extra)
|
source = fmt.Sprintf("%s&%s", source, config.Extra)
|
||||||
@ -89,7 +97,7 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||||
return `"`, `"`
|
return quoteChar, quoteChar
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
|
func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) {
|
||||||
@ -169,9 +177,9 @@ func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args [
|
|||||||
// There should be no need to capitalize, because it has been done from field processing before
|
// There should be no need to capitalize, because it has been done from field processing before
|
||||||
newSql, err = gregex.ReplaceString(`["\n\t]`, "", sql)
|
newSql, err = gregex.ReplaceString(`["\n\t]`, "", sql)
|
||||||
newSql = gstr.ReplaceI(newSql, "GROUP_CONCAT", "WM_CONCAT")
|
newSql = gstr.ReplaceI(newSql, "GROUP_CONCAT", "WM_CONCAT")
|
||||||
// g.Dump("Driver.DoFilter()::newSql", newSql)
|
// gutil.Dump("Driver.DoFilter()::newSql", newSql)
|
||||||
newArgs = args
|
newArgs = args
|
||||||
// g.Dump("Driver.DoFilter()::newArgs", newArgs)
|
// gutil.Dump("Driver.DoFilter()::newArgs", newArgs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,23 +284,7 @@ func parseValue(listOne gdb.Map, char struct {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
va := reflect.ValueOf(listOne[column])
|
saveValue := gconv.String(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.
|
|
||||||
}
|
|
||||||
queryValues = append(
|
queryValues = append(
|
||||||
queryValues,
|
queryValues,
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
|
|||||||
@ -125,9 +125,9 @@ func createTable(table ...string) (name string) {
|
|||||||
"ENABLED" INT DEFAULT 1 NOT NULL,
|
"ENABLED" INT DEFAULT 1 NOT NULL,
|
||||||
"DELETED" INT DEFAULT 0 NOT NULL,
|
"DELETED" INT DEFAULT 0 NOT NULL,
|
||||||
"CREATED_BY" VARCHAR(32) DEFAULT '' 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_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) ;
|
NOT CLUSTER PRIMARY KEY("ID")) STORAGE(ON "MAIN", CLUSTERBTR) ;
|
||||||
`, name)); err != nil {
|
`, name)); err != nil {
|
||||||
gtest.Fatal(err)
|
gtest.Fatal(err)
|
||||||
|
|||||||
@ -233,7 +233,7 @@ func Test_DB_Insert(t *testing.T) {
|
|||||||
_, err := db.Insert(ctx, "A_tables", g.Map{
|
_, err := db.Insert(ctx, "A_tables", g.Map{
|
||||||
"ID": 1000,
|
"ID": 1000,
|
||||||
"ACCOUNT_NAME": "map1",
|
"ACCOUNT_NAME": "map1",
|
||||||
"CREATED_TIME": gtime.Now().String(),
|
"CREATED_TIME": gtime.Now(),
|
||||||
})
|
})
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
|
|
||||||
@ -249,7 +249,7 @@ func Test_DB_Insert(t *testing.T) {
|
|||||||
result, err = db.Insert(ctx, "A_tables", g.Map{
|
result, err = db.Insert(ctx, "A_tables", g.Map{
|
||||||
"ID": 3000,
|
"ID": 3000,
|
||||||
"ACCOUNT_NAME": "map3",
|
"ACCOUNT_NAME": "map3",
|
||||||
// "CREATED_TIME": gtime.Now().String(),
|
// "CREATED_TIME": gtime.Now(),
|
||||||
})
|
})
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
n, _ = result.RowsAffected()
|
n, _ = result.RowsAffected()
|
||||||
@ -320,12 +320,12 @@ func Test_DB_BatchInsert(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"ID": 400,
|
"ID": 400,
|
||||||
"ACCOUNT_NAME": "list_400",
|
"ACCOUNT_NAME": "list_400",
|
||||||
// "CREATE_TIME": gtime.Now().String(),
|
// "CREATE_TIME": gtime.Now(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ID": 401,
|
"ID": 401,
|
||||||
"ACCOUNT_NAME": "list_401",
|
"ACCOUNT_NAME": "list_401",
|
||||||
"CREATE_TIME": gtime.Now().String(),
|
"CREATE_TIME": gtime.Now(),
|
||||||
},
|
},
|
||||||
}, 1)
|
}, 1)
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
@ -342,12 +342,12 @@ func Test_DB_BatchInsert(t *testing.T) {
|
|||||||
g.Map{
|
g.Map{
|
||||||
"ID": 500,
|
"ID": 500,
|
||||||
"ACCOUNT_NAME": "500_batch_500",
|
"ACCOUNT_NAME": "500_batch_500",
|
||||||
"CREATE_TIME": gtime.Now().String(),
|
"CREATE_TIME": gtime.Now(),
|
||||||
},
|
},
|
||||||
g.Map{
|
g.Map{
|
||||||
"ID": 501,
|
"ID": 501,
|
||||||
"ACCOUNT_NAME": "501_batch_501",
|
"ACCOUNT_NAME": "501_batch_501",
|
||||||
// "CREATE_TIME": gtime.Now().String(),
|
// "CREATE_TIME": gtime.Now(),
|
||||||
},
|
},
|
||||||
}, 1)
|
}, 1)
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
@ -363,7 +363,7 @@ func Test_DB_BatchInsert(t *testing.T) {
|
|||||||
result, err := db.Insert(ctx, table, g.Map{
|
result, err := db.Insert(ctx, table, g.Map{
|
||||||
"ID": 600,
|
"ID": 600,
|
||||||
"ACCOUNT_NAME": "600_batch_600",
|
"ACCOUNT_NAME": "600_batch_600",
|
||||||
"CREATE_TIME": gtime.Now().String(),
|
"CREATE_TIME": gtime.Now(),
|
||||||
})
|
})
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
n, _ := result.RowsAffected()
|
n, _ := result.RowsAffected()
|
||||||
@ -582,125 +582,3 @@ func Test_Empty_Slice_Argument(t *testing.T) {
|
|||||||
t.Assert(len(result), 0)
|
t.Assert(len(result), 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// func Test_GROUP_CONCAT(t *testing.T) {
|
|
||||||
// gtest.C(t, func(t *gtest.T) {
|
|
||||||
// type GroupIdAndUserIDsInfo struct {
|
|
||||||
// GroupID int64
|
|
||||||
// UserIDs string
|
|
||||||
// }
|
|
||||||
// result := make([]GroupIdAndUserIDsInfo, 0)
|
|
||||||
|
|
||||||
// model := db.Model("t_inf_group", "groupinfo").Fields("groupinfo.id as group_id", "GROUP_CONCAT(userinfo.id) as user_ids")
|
|
||||||
// model.InnerJoin("t_lin_user_group", "lin", "groupinfo.id = lin.group_id")
|
|
||||||
// model.InnerJoin("t_inf_user", "userinfo", "lin.user_id = userinfo.id")
|
|
||||||
// model.Where("groupinfo.enabled", 1).Where("groupinfo.deleted", 0)
|
|
||||||
// model.Where("userinfo.enabled", 1).Where("userinfo.deleted", 0)
|
|
||||||
// model.Group("groupinfo.id")
|
|
||||||
|
|
||||||
// err := model.Scan(&result)
|
|
||||||
// gtest.Assert(err, nil)
|
|
||||||
// g.Dump(result)
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestGroup(t *testing.T) {
|
|
||||||
// gtest.C(t, func(t *gtest.T) {
|
|
||||||
// type GroupListResult struct {
|
|
||||||
// ID int64 `json:"group_id"`
|
|
||||||
// GroupName string `json:"group_name"`
|
|
||||||
// CategoryName string `json:"category_name"`
|
|
||||||
// Description string `json:"description"`
|
|
||||||
// RoleName string `json:"role_name"`
|
|
||||||
// UserIDs []string `json:"user_ids"`
|
|
||||||
// Enabled int64 `json:"enabled"`
|
|
||||||
// CreatedTime string `json:"created_time"`
|
|
||||||
// UpdateTime string `json:"updated_time"`
|
|
||||||
// }
|
|
||||||
// result := make([]GroupListResult, 0)
|
|
||||||
|
|
||||||
// model := db.Model("t_inf_group", "groupinfo")
|
|
||||||
// model.LeftJoin("t_inf_group_category", "category", "groupinfo.category_id=category.id and (category.enabled = 1) and (category.deleted = 0)").
|
|
||||||
// Where("groupinfo.deleted", 0).
|
|
||||||
// Where("groupinfo.enabled", 1)
|
|
||||||
|
|
||||||
// total, err := model.Count()
|
|
||||||
// gtest.Assert(err, nil)
|
|
||||||
// model.Fields("distinct groupinfo.id, groupinfo.group_name, groupinfo.enabled, ifnull(category.category_name,'') as category_name", "groupinfo.created_time", "groupinfo.updated_time", "groupinfo.description")
|
|
||||||
// err = model.Order("groupinfo.updated_time desc").Page(1, 100).Scan(&result)
|
|
||||||
// gtest.Assert(err, nil)
|
|
||||||
// g.Dump(result)
|
|
||||||
// g.Dump(total)
|
|
||||||
// })
|
|
||||||
|
|
||||||
// gtest.C(t, func(t *gtest.T) {
|
|
||||||
// type GroupListByUserIdResult struct {
|
|
||||||
// ID int64 `json:"group_id"`
|
|
||||||
// GroupName string `json:"group_name"`
|
|
||||||
// CategoryName string `json:"category_name"`
|
|
||||||
// RoleName string `json:"role_name"`
|
|
||||||
// }
|
|
||||||
// result := make([]*GroupListByUserIdResult, 0)
|
|
||||||
|
|
||||||
// model := db.Model("t_inf_group", "groupinfo").Fields("distinct groupinfo.id, groupinfo.group_name, groupinfo.enabled, category.category_name,groupinfo.updated_time")
|
|
||||||
|
|
||||||
// model.LeftJoin("t_inf_group_category", "category", "groupinfo.category_id=category.id and (category.enabled = 1) and (category.deleted = 0)")
|
|
||||||
|
|
||||||
// // if userId != 0 {
|
|
||||||
// // model.InnerJoin(grouptype.TLINUSERGROUP, "ug", "groupinfo.id=ug.group_id")
|
|
||||||
// // model.InnerJoin(grouptype.TINFUSER, "u", "u.id=ug.user_id")
|
|
||||||
// // model.Where("u.id = ?", userId).Where("u.deleted", consts.DataDeletedFalse)
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// model.Where("groupinfo.enabled", 1).Where("groupinfo.deleted", 0)
|
|
||||||
|
|
||||||
// err := model.Order("groupinfo.updated_time desc").Scan(&result)
|
|
||||||
// //
|
|
||||||
// gtest.Assert(err, nil)
|
|
||||||
// g.Dump(result)
|
|
||||||
// })
|
|
||||||
// gtest.C(t, func(t *gtest.T) {
|
|
||||||
|
|
||||||
// model := db.Model("t_inf_role", "role").Fields("role.role_name", "role.id")
|
|
||||||
// model.RightJoin("t_lin_group_role", "link", "link.role_id=role.id")
|
|
||||||
// // model.Where("link.group_id", gid)
|
|
||||||
// model.Where("role.deleted", 0)
|
|
||||||
|
|
||||||
// record, err := model.One()
|
|
||||||
// gtest.Assert(err, nil)
|
|
||||||
// g.Dump(record)
|
|
||||||
|
|
||||||
// })
|
|
||||||
// gtest.C(t, func(t *gtest.T) {
|
|
||||||
// type GroupInfos struct {
|
|
||||||
// RoleName string `orm:"role_name"`
|
|
||||||
// RoleID int64 `orm:"id"`
|
|
||||||
// GroupID int64 `orm:"group_id"`
|
|
||||||
// }
|
|
||||||
// result := make([]GroupInfos, 0)
|
|
||||||
|
|
||||||
// model := db.Model("t_inf_role", "role").Fields("role.id", "role.role_name", "link.group_id")
|
|
||||||
// model.RightJoin("t_lin_group_role", "link", "link.role_id=role.id")
|
|
||||||
// model.Where("role.enabled", 1).Where("role.deleted", 0)
|
|
||||||
// err := model.Scan(&result)
|
|
||||||
// gtest.Assert(err, nil)
|
|
||||||
// g.Dump(result)
|
|
||||||
// })
|
|
||||||
// gtest.C(t, func(t *gtest.T) {
|
|
||||||
// type GroupIdAndRoleNameInfo struct {
|
|
||||||
// GroupID int64
|
|
||||||
// RoleID int64
|
|
||||||
// RoleName string
|
|
||||||
// }
|
|
||||||
// result := make([]GroupIdAndRoleNameInfo, 0)
|
|
||||||
// model := db.Model("t_inf_group", "groupinfo").Fields("groupinfo.id as group_id", "lin.role_id", "role.role_name")
|
|
||||||
// model.InnerJoin("t_lin_group_role", "lin", "groupinfo.id = lin.group_id")
|
|
||||||
// model.InnerJoin("t_inf_role", "role", "lin.role_id = role.id")
|
|
||||||
// model.Where("groupinfo.enabled", 1).Where("groupinfo.deleted", 0)
|
|
||||||
// model.Where("role.enabled", 1).Where("role.deleted", 0)
|
|
||||||
|
|
||||||
// err2 := model.Scan(&result)
|
|
||||||
// gtest.Assert(err2, nil)
|
|
||||||
// g.Dump(result)
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|||||||
@ -5,6 +5,6 @@ go 1.15
|
|||||||
replace github.com/gogf/gf/v2 => ../../../
|
replace github.com/gogf/gf/v2 => ../../../
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitee.com/chunanyong/dm v1.8.6
|
gitee.com/chunanyong/dm v1.8.10
|
||||||
github.com/gogf/gf/v2 v2.0.0
|
github.com/gogf/gf/v2 v2.0.0
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
gitee.com/chunanyong/dm v1.8.6 h1:5UnOCW1f2+LYiSQvuHiloS6OTMnZAtjRQ4woi9i6QY4=
|
gitee.com/chunanyong/dm v1.8.10 h1:9S1CKUggWHIea/GI7nr7S/DNMaxIilNFgfzdzKDx2+I=
|
||||||
gitee.com/chunanyong/dm v1.8.6/go.mod h1:EPRJnuPFgbyOFgJ0TRYCTGzhq+ZT4wdyaj/GW/LLcNg=
|
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 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
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 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
|
||||||
|
|||||||
@ -33,6 +33,10 @@ type Driver struct {
|
|||||||
*gdb.Core
|
*gdb.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
quoteChar = `"`
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := gdb.Register(`mssql`, New()); err != nil {
|
if err := gdb.Register(`mssql`, New()); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -95,7 +99,7 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
|||||||
|
|
||||||
// GetChars returns the security char for this type of database.
|
// GetChars returns the security char for this type of database.
|
||||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||||
return `"`, `"`
|
return quoteChar, quoteChar
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||||
@ -237,11 +241,11 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
|
|||||||
// Also see DriverMysql.TableFields.
|
// Also see DriverMysql.TableFields.
|
||||||
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
|
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
|
||||||
var (
|
var (
|
||||||
result gdb.Result
|
result gdb.Result
|
||||||
link gdb.Link
|
link gdb.Link
|
||||||
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||||
)
|
)
|
||||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
structureSql := fmt.Sprintf(`
|
structureSql := fmt.Sprintf(`
|
||||||
|
|||||||
@ -107,6 +107,8 @@ func createTable(table ...string) (name string) {
|
|||||||
PASSWORD VARCHAR(32) NULL,
|
PASSWORD VARCHAR(32) NULL,
|
||||||
NICKNAME VARCHAR(45) NULL,
|
NICKNAME VARCHAR(45) NULL,
|
||||||
CREATE_TIME datetime NULL,
|
CREATE_TIME datetime NULL,
|
||||||
|
CREATED_AT datetimeoffset NULL,
|
||||||
|
UPDATED_AT datetimeoffset NULL,
|
||||||
PRIMARY KEY (ID))
|
PRIMARY KEY (ID))
|
||||||
`, name, name)); err != nil {
|
`, name, name)); err != nil {
|
||||||
gtest.Fatal(err)
|
gtest.Fatal(err)
|
||||||
@ -125,7 +127,7 @@ func createInitTable(table ...string) (name string) {
|
|||||||
"passport": fmt.Sprintf(`user_%d`, i),
|
"passport": fmt.Sprintf(`user_%d`, i),
|
||||||
"password": fmt.Sprintf(`pass_%d`, i),
|
"password": fmt.Sprintf(`pass_%d`, i),
|
||||||
"nickname": fmt.Sprintf(`name_%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())
|
result, err := db.Insert(context.Background(), name, array.Slice())
|
||||||
|
|||||||
@ -120,7 +120,7 @@ func TestDoInsert(t *testing.T) {
|
|||||||
"passport": fmt.Sprintf(`t%d`, i),
|
"passport": fmt.Sprintf(`t%d`, i),
|
||||||
"password": fmt.Sprintf(`p%d`, i),
|
"password": fmt.Sprintf(`p%d`, i),
|
||||||
"nickname": fmt.Sprintf(`T%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)
|
_, err := db.Insert(context.Background(), "t_user", data)
|
||||||
gtest.Assert(err, nil)
|
gtest.Assert(err, nil)
|
||||||
@ -137,7 +137,7 @@ func TestDoInsert(t *testing.T) {
|
|||||||
"passport": fmt.Sprintf(`t%d`, i),
|
"passport": fmt.Sprintf(`t%d`, i),
|
||||||
"password": fmt.Sprintf(`p%d`, i),
|
"password": fmt.Sprintf(`p%d`, i),
|
||||||
"nickname": fmt.Sprintf(`T%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)
|
_, err := db.Save(context.Background(), "t_user", data, 10)
|
||||||
gtest.AssertNE(err, nil)
|
gtest.AssertNE(err, nil)
|
||||||
@ -192,7 +192,7 @@ func Test_DB_Insert(t *testing.T) {
|
|||||||
"passport": "t1",
|
"passport": "t1",
|
||||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||||
"nickname": "T1",
|
"nickname": "T1",
|
||||||
"create_time": gtime.Now().String(),
|
"create_time": gtime.Now(),
|
||||||
})
|
})
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ func Test_DB_Insert(t *testing.T) {
|
|||||||
"passport": "t2",
|
"passport": "t2",
|
||||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||||
"nickname": "name_2",
|
"nickname": "name_2",
|
||||||
"create_time": gtime.Now().String(),
|
"create_time": gtime.Now(),
|
||||||
})
|
})
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
n, _ := result.RowsAffected()
|
n, _ := result.RowsAffected()
|
||||||
@ -210,19 +210,19 @@ func Test_DB_Insert(t *testing.T) {
|
|||||||
|
|
||||||
// struct
|
// struct
|
||||||
type User struct {
|
type User struct {
|
||||||
Id int `gconv:"id"`
|
Id int `gconv:"id"`
|
||||||
Passport string `json:"passport"`
|
Passport string `json:"passport"`
|
||||||
Password string `gconv:"password"`
|
Password string `gconv:"password"`
|
||||||
Nickname string `gconv:"nickname"`
|
Nickname string `gconv:"nickname"`
|
||||||
CreateTime string `json:"create_time"`
|
CreateTime *gtime.Time `json:"create_time"`
|
||||||
}
|
}
|
||||||
timeStr := gtime.Now().String()
|
timeNow := gtime.Now()
|
||||||
result, err = db.Insert(ctx, table, User{
|
result, err = db.Insert(ctx, table, User{
|
||||||
Id: 3,
|
Id: 3,
|
||||||
Passport: "user_3",
|
Passport: "user_3",
|
||||||
Password: "25d55ad283aa400af464c76d713c07ad",
|
Password: "25d55ad283aa400af464c76d713c07ad",
|
||||||
Nickname: "name_3",
|
Nickname: "name_3",
|
||||||
CreateTime: timeStr,
|
CreateTime: timeNow,
|
||||||
})
|
})
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
n, _ = result.RowsAffected()
|
n, _ = result.RowsAffected()
|
||||||
@ -235,16 +235,16 @@ func Test_DB_Insert(t *testing.T) {
|
|||||||
t.Assert(one["PASSPORT"].String(), "user_3")
|
t.Assert(one["PASSPORT"].String(), "user_3")
|
||||||
t.Assert(one["PASSWORD"].String(), "25d55ad283aa400af464c76d713c07ad")
|
t.Assert(one["PASSWORD"].String(), "25d55ad283aa400af464c76d713c07ad")
|
||||||
t.Assert(one["NICKNAME"].String(), "name_3")
|
t.Assert(one["NICKNAME"].String(), "name_3")
|
||||||
t.Assert(one["CREATE_TIME"].GTime().String(), timeStr)
|
t.Assert(one["CREATE_TIME"].GTime(), timeNow)
|
||||||
|
|
||||||
// *struct
|
// *struct
|
||||||
timeStr = gtime.Now().String()
|
timeNow = gtime.Now()
|
||||||
result, err = db.Insert(ctx, table, &User{
|
result, err = db.Insert(ctx, table, &User{
|
||||||
Id: 4,
|
Id: 4,
|
||||||
Passport: "t4",
|
Passport: "t4",
|
||||||
Password: "25d55ad283aa400af464c76d713c07ad",
|
Password: "25d55ad283aa400af464c76d713c07ad",
|
||||||
Nickname: "name_4",
|
Nickname: "name_4",
|
||||||
CreateTime: timeStr,
|
CreateTime: timeNow,
|
||||||
})
|
})
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
n, _ = result.RowsAffected()
|
n, _ = result.RowsAffected()
|
||||||
@ -256,24 +256,24 @@ func Test_DB_Insert(t *testing.T) {
|
|||||||
t.Assert(one["PASSPORT"].String(), "t4")
|
t.Assert(one["PASSPORT"].String(), "t4")
|
||||||
t.Assert(one["PASSWORD"].String(), "25d55ad283aa400af464c76d713c07ad")
|
t.Assert(one["PASSWORD"].String(), "25d55ad283aa400af464c76d713c07ad")
|
||||||
t.Assert(one["NICKNAME"].String(), "name_4")
|
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
|
// batch with Insert
|
||||||
timeStr = gtime.Now().String()
|
timeNow = gtime.Now()
|
||||||
r, err := db.Insert(ctx, table, g.Slice{
|
r, err := db.Insert(ctx, table, g.Slice{
|
||||||
g.Map{
|
g.Map{
|
||||||
"id": 200,
|
"id": 200,
|
||||||
"passport": "t200",
|
"passport": "t200",
|
||||||
"password": "25d55ad283aa400af464c76d71qw07ad",
|
"password": "25d55ad283aa400af464c76d71qw07ad",
|
||||||
"nickname": "T200",
|
"nickname": "T200",
|
||||||
"create_time": timeStr,
|
"create_time": timeNow,
|
||||||
},
|
},
|
||||||
g.Map{
|
g.Map{
|
||||||
"id": 300,
|
"id": 300,
|
||||||
"passport": "t300",
|
"passport": "t300",
|
||||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||||
"nickname": "T300",
|
"nickname": "T300",
|
||||||
"create_time": timeStr,
|
"create_time": timeNow,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
@ -286,7 +286,7 @@ func Test_DB_Insert(t *testing.T) {
|
|||||||
t.Assert(one["PASSPORT"].String(), "t200")
|
t.Assert(one["PASSPORT"].String(), "t200")
|
||||||
t.Assert(one["PASSWORD"].String(), "25d55ad283aa400af464c76d71qw07ad")
|
t.Assert(one["PASSWORD"].String(), "25d55ad283aa400af464c76d71qw07ad")
|
||||||
t.Assert(one["NICKNAME"].String(), "T200")
|
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",
|
"passport": "t2",
|
||||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||||
"nickname": "name_2",
|
"nickname": "name_2",
|
||||||
"create_time": gtime.Now().String(),
|
"create_time": gtime.Now(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"passport": "user_3",
|
"passport": "user_3",
|
||||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||||
"nickname": "name_3",
|
"nickname": "name_3",
|
||||||
"create_time": gtime.Now().String(),
|
"create_time": gtime.Now(),
|
||||||
},
|
},
|
||||||
}, 1)
|
}, 1)
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
@ -386,14 +386,14 @@ func Test_DB_BatchInsert(t *testing.T) {
|
|||||||
"passport": "t2",
|
"passport": "t2",
|
||||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||||
"nickname": "name_2",
|
"nickname": "name_2",
|
||||||
"create_time": gtime.Now().String(),
|
"create_time": gtime.Now(),
|
||||||
},
|
},
|
||||||
g.Map{
|
g.Map{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"passport": "user_3",
|
"passport": "user_3",
|
||||||
"password": "25d55ad283aa400af464c76d713c07ad",
|
"password": "25d55ad283aa400af464c76d713c07ad",
|
||||||
"nickname": "name_3",
|
"nickname": "name_3",
|
||||||
"create_time": gtime.Now().String(),
|
"create_time": gtime.Now(),
|
||||||
},
|
},
|
||||||
}, 1)
|
}, 1)
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
@ -410,7 +410,7 @@ func Test_DB_BatchInsert(t *testing.T) {
|
|||||||
"passport": "t1",
|
"passport": "t1",
|
||||||
"password": "p1",
|
"password": "p1",
|
||||||
"nickname": "T1",
|
"nickname": "T1",
|
||||||
"create_time": gtime.Now().String(),
|
"create_time": gtime.Now(),
|
||||||
})
|
})
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
n, _ := result.RowsAffected()
|
n, _ := result.RowsAffected()
|
||||||
|
|||||||
@ -1556,15 +1556,15 @@ func Test_Model_Option_Map(t *testing.T) {
|
|||||||
t.Assert(n, 1)
|
t.Assert(n, 1)
|
||||||
|
|
||||||
_, err = db.Model(table).OmitEmptyData().Data(g.Map{"nickname": ""}).Where("id", 2).Update()
|
_, 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()
|
r, err = db.Model(table).OmitEmpty().Data(g.Map{"nickname": "", "password": "123"}).Where("id", 3).Update()
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
n, _ = r.RowsAffected()
|
n, _ = r.RowsAffected()
|
||||||
t.Assert(n, 1)
|
t.Assert(n, 1)
|
||||||
|
|
||||||
_, err = db.Model(table).OmitEmpty().Fields("nickname").Data(g.Map{"nickname": "", "password": "123"}).Where("id", 4).Update()
|
_, err = db.Model(table).OmitEmpty().Fields("nickname", "password").Data(g.Map{"nickname": "", "password": "123", "passport": "123"}).Where("id", 4).Update()
|
||||||
t.AssertNE(err, nil)
|
t.AssertNil(err)
|
||||||
|
|
||||||
r, err = db.Model(table).OmitEmpty().
|
r, err = db.Model(table).OmitEmpty().
|
||||||
Fields("password").Data(g.Map{
|
Fields("password").Data(g.Map{
|
||||||
@ -1639,7 +1639,7 @@ func Test_Model_FieldsEx(t *testing.T) {
|
|||||||
defer dropTable(table)
|
defer dropTable(table)
|
||||||
// Select.
|
// Select.
|
||||||
gtest.C(t, func(t *gtest.T) {
|
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.AssertNil(err)
|
||||||
t.Assert(len(r), 2)
|
t.Assert(len(r), 2)
|
||||||
t.Assert(len(r[0]), 3)
|
t.Assert(len(r[0]), 3)
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
"github.com/gogf/gf/v2/database/gdb"
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
@ -27,6 +28,10 @@ type Driver struct {
|
|||||||
*gdb.Core
|
*gdb.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
quoteChar = "`"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
@ -76,7 +81,10 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
|||||||
config.User, config.Pass, config.Protocol, config.Host, config.Port, config.Name, config.Charset,
|
config.User, config.Pass, config.Protocol, config.Host, config.Port, config.Name, config.Charset,
|
||||||
)
|
)
|
||||||
if config.Timezone != "" {
|
if config.Timezone != "" {
|
||||||
source = fmt.Sprintf("%s&loc=%s", source, url.QueryEscape(config.Timezone))
|
if strings.Contains(config.Timezone, "/") {
|
||||||
|
config.Timezone = url.QueryEscape(config.Timezone)
|
||||||
|
}
|
||||||
|
source = fmt.Sprintf("%s&loc=%s", source, config.Timezone)
|
||||||
}
|
}
|
||||||
if config.Extra != "" {
|
if config.Extra != "" {
|
||||||
source = fmt.Sprintf("%s&%s", source, config.Extra)
|
source = fmt.Sprintf("%s&%s", source, config.Extra)
|
||||||
@ -94,7 +102,7 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
|||||||
|
|
||||||
// GetChars returns the security char for this type of database.
|
// GetChars returns the security char for this type of database.
|
||||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||||
return "`", "`"
|
return quoteChar, quoteChar
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoFilter handles the sql before posts it to database.
|
// DoFilter handles the sql before posts it to database.
|
||||||
@ -138,11 +146,11 @@ func (d *Driver) TableFields(
|
|||||||
ctx context.Context, table string, schema ...string,
|
ctx context.Context, table string, schema ...string,
|
||||||
) (fields map[string]*gdb.TableField, err error) {
|
) (fields map[string]*gdb.TableField, err error) {
|
||||||
var (
|
var (
|
||||||
result gdb.Result
|
result gdb.Result
|
||||||
link gdb.Link
|
link gdb.Link
|
||||||
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||||
)
|
)
|
||||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result, err = d.DoSelect(
|
result, err = d.DoSelect(
|
||||||
|
|||||||
@ -117,7 +117,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
|
|||||||
passport varchar(45) NULL,
|
passport varchar(45) NULL,
|
||||||
password char(32) NULL,
|
password char(32) NULL,
|
||||||
nickname varchar(45) NULL,
|
nickname varchar(45) NULL,
|
||||||
create_time timestamp NULL,
|
create_time timestamp(6) NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
`, name,
|
`, name,
|
||||||
|
|||||||
@ -9,8 +9,11 @@ package mysql_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/encoding/gjson"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/os/gtime"
|
||||||
"github.com/gogf/gf/v2/test/gtest"
|
"github.com/gogf/gf/v2/test/gtest"
|
||||||
|
"github.com/gogf/gf/v2/util/gmeta"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Model_Builder(t *testing.T) {
|
func Test_Model_Builder(t *testing.T) {
|
||||||
@ -59,4 +62,66 @@ func Test_Model_Builder(t *testing.T) {
|
|||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
t.Assert(len(all), 6)
|
t.Assert(len(all), 6)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Where with struct which has a field type of *gtime.Time
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
m := db.Model(table)
|
||||||
|
b := m.Builder()
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
Id interface{}
|
||||||
|
Nickname *gtime.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
where, args := b.Where(&Query{Id: 1}).Build()
|
||||||
|
t.Assert(where, "`id`=? AND `nickname` IS NULL")
|
||||||
|
t.Assert(args, []interface{}{1})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Where with struct which has a field type of *gjson.Json
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
m := db.Model(table)
|
||||||
|
b := m.Builder()
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
Id interface{}
|
||||||
|
Nickname *gjson.Json
|
||||||
|
}
|
||||||
|
|
||||||
|
where, args := b.Where(&Query{Id: 1}).Build()
|
||||||
|
t.Assert(where, "`id`=? AND `nickname` IS NULL")
|
||||||
|
t.Assert(args, []interface{}{1})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Where with do struct which has a field type of *gtime.Time and generated by gf cli
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
m := db.Model(table)
|
||||||
|
b := m.Builder()
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
gmeta.Meta `orm:"do:true"`
|
||||||
|
Id interface{}
|
||||||
|
Nickname *gtime.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
where, args := b.Where(&Query{Id: 1}).Build()
|
||||||
|
t.Assert(where, "`id`=?")
|
||||||
|
t.Assert(args, []interface{}{1})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Where with do struct which has a field type of *gjson.Json and generated by gf cli
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
m := db.Model(table)
|
||||||
|
b := m.Builder()
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
gmeta.Meta `orm:"do:true"`
|
||||||
|
Id interface{}
|
||||||
|
Nickname *gjson.Json
|
||||||
|
}
|
||||||
|
|
||||||
|
where, args := b.Where(&Query{Id: 1}).Build()
|
||||||
|
t.Assert(where, "`id`=?")
|
||||||
|
t.Assert(args, []interface{}{1})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,15 +17,149 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// CreateAt/UpdateAt/DeleteAt.
|
// CreateAt/UpdateAt/DeleteAt.
|
||||||
func Test_SoftCreateUpdateDeleteTime(t *testing.T) {
|
func Test_SoftCreateUpdateDeleteTimeMicroSecond(t *testing.T) {
|
||||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||||
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||||
CREATE TABLE %s (
|
CREATE TABLE %s (
|
||||||
id int(11) NOT NULL,
|
id int(11) NOT NULL,
|
||||||
name varchar(45) DEFAULT NULL,
|
name varchar(45) DEFAULT NULL,
|
||||||
create_at datetime DEFAULT NULL,
|
create_at datetime(6) DEFAULT NULL,
|
||||||
update_at datetime DEFAULT NULL,
|
update_at datetime(6) DEFAULT NULL,
|
||||||
delete_at datetime 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)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
`, table)); err != nil {
|
`, table)); err != nil {
|
||||||
@ -157,9 +291,9 @@ func Test_SoftCreatedUpdatedDeletedTime_Map(t *testing.T) {
|
|||||||
CREATE TABLE %s (
|
CREATE TABLE %s (
|
||||||
id int(11) NOT NULL,
|
id int(11) NOT NULL,
|
||||||
name varchar(45) DEFAULT NULL,
|
name varchar(45) DEFAULT NULL,
|
||||||
created_at datetime DEFAULT NULL,
|
created_at datetime(6) DEFAULT NULL,
|
||||||
updated_at datetime DEFAULT NULL,
|
updated_at datetime(6) DEFAULT NULL,
|
||||||
deleted_at datetime DEFAULT NULL,
|
deleted_at datetime(6) DEFAULT NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
`, table)); err != nil {
|
`, table)); err != nil {
|
||||||
@ -291,9 +425,9 @@ func Test_SoftCreatedUpdatedDeletedTime_Struct(t *testing.T) {
|
|||||||
CREATE TABLE %s (
|
CREATE TABLE %s (
|
||||||
id int(11) NOT NULL,
|
id int(11) NOT NULL,
|
||||||
name varchar(45) DEFAULT NULL,
|
name varchar(45) DEFAULT NULL,
|
||||||
created_at datetime DEFAULT NULL,
|
created_at datetime(6) DEFAULT NULL,
|
||||||
updated_at datetime DEFAULT NULL,
|
updated_at datetime(6) DEFAULT NULL,
|
||||||
deleted_at datetime DEFAULT NULL,
|
deleted_at datetime(6) DEFAULT NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
`, table)); err != nil {
|
`, table)); err != nil {
|
||||||
@ -431,9 +565,9 @@ func Test_SoftUpdateTime(t *testing.T) {
|
|||||||
CREATE TABLE %s (
|
CREATE TABLE %s (
|
||||||
id int(11) NOT NULL,
|
id int(11) NOT NULL,
|
||||||
num int(11) DEFAULT NULL,
|
num int(11) DEFAULT NULL,
|
||||||
create_at datetime DEFAULT NULL,
|
create_at datetime(6) DEFAULT NULL,
|
||||||
update_at datetime DEFAULT NULL,
|
update_at datetime(6) DEFAULT NULL,
|
||||||
delete_at datetime DEFAULT NULL,
|
delete_at datetime(6) DEFAULT NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
`, table)); err != nil {
|
`, table)); err != nil {
|
||||||
@ -471,9 +605,9 @@ func Test_SoftUpdateTime_WithDO(t *testing.T) {
|
|||||||
CREATE TABLE %s (
|
CREATE TABLE %s (
|
||||||
id int(11) NOT NULL,
|
id int(11) NOT NULL,
|
||||||
num int(11) DEFAULT NULL,
|
num int(11) DEFAULT NULL,
|
||||||
created_at datetime DEFAULT NULL,
|
created_at datetime(6) DEFAULT NULL,
|
||||||
updated_at datetime DEFAULT NULL,
|
updated_at datetime(6) DEFAULT NULL,
|
||||||
deleted_at datetime DEFAULT NULL,
|
deleted_at datetime(6) DEFAULT NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
`, table)); err != nil {
|
`, table)); err != nil {
|
||||||
@ -528,9 +662,9 @@ func Test_SoftDelete(t *testing.T) {
|
|||||||
CREATE TABLE %s (
|
CREATE TABLE %s (
|
||||||
id int(11) NOT NULL,
|
id int(11) NOT NULL,
|
||||||
name varchar(45) DEFAULT NULL,
|
name varchar(45) DEFAULT NULL,
|
||||||
create_at datetime DEFAULT NULL,
|
create_at datetime(6) DEFAULT NULL,
|
||||||
update_at datetime DEFAULT NULL,
|
update_at datetime(6) DEFAULT NULL,
|
||||||
delete_at datetime DEFAULT NULL,
|
delete_at datetime(6) DEFAULT NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
`, table)); err != nil {
|
`, table)); err != nil {
|
||||||
@ -596,9 +730,9 @@ func Test_SoftDelete_Join(t *testing.T) {
|
|||||||
CREATE TABLE %s (
|
CREATE TABLE %s (
|
||||||
id int(11) NOT NULL,
|
id int(11) NOT NULL,
|
||||||
name varchar(45) DEFAULT NULL,
|
name varchar(45) DEFAULT NULL,
|
||||||
create_at datetime DEFAULT NULL,
|
create_at datetime(6) DEFAULT NULL,
|
||||||
update_at datetime DEFAULT NULL,
|
update_at datetime(6) DEFAULT NULL,
|
||||||
delete_at datetime DEFAULT NULL,
|
delete_at datetime(6) DEFAULT NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
`, table1)); err != nil {
|
`, table1)); err != nil {
|
||||||
@ -611,9 +745,9 @@ CREATE TABLE %s (
|
|||||||
CREATE TABLE %s (
|
CREATE TABLE %s (
|
||||||
id int(11) NOT NULL,
|
id int(11) NOT NULL,
|
||||||
name varchar(45) DEFAULT NULL,
|
name varchar(45) DEFAULT NULL,
|
||||||
createat datetime DEFAULT NULL,
|
createat datetime(6) DEFAULT NULL,
|
||||||
updateat datetime DEFAULT NULL,
|
updateat datetime(6) DEFAULT NULL,
|
||||||
deleteat datetime DEFAULT NULL,
|
deleteat datetime(6) DEFAULT NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
`, table2)); err != nil {
|
`, table2)); err != nil {
|
||||||
@ -646,7 +780,7 @@ CREATE TABLE %s (
|
|||||||
t.Assert(one["name"], "name_1")
|
t.Assert(one["name"], "name_1")
|
||||||
|
|
||||||
// Soft deleting.
|
// Soft deleting.
|
||||||
r, err = db.Model(table1).Delete()
|
r, err = db.Model(table1).Where(1).Delete()
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
n, _ = r.RowsAffected()
|
n, _ = r.RowsAffected()
|
||||||
t.Assert(n, 1)
|
t.Assert(n, 1)
|
||||||
@ -667,9 +801,9 @@ func Test_SoftDelete_WhereAndOr(t *testing.T) {
|
|||||||
CREATE TABLE %s (
|
CREATE TABLE %s (
|
||||||
id int(11) NOT NULL,
|
id int(11) NOT NULL,
|
||||||
name varchar(45) DEFAULT NULL,
|
name varchar(45) DEFAULT NULL,
|
||||||
create_at datetime DEFAULT NULL,
|
create_at datetime(6) DEFAULT NULL,
|
||||||
update_at datetime DEFAULT NULL,
|
update_at datetime(6) DEFAULT NULL,
|
||||||
delete_at datetime DEFAULT NULL,
|
delete_at datetime(6) DEFAULT NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
`, table)); err != nil {
|
`, table)); err != nil {
|
||||||
@ -709,9 +843,9 @@ func Test_CreateUpdateTime_Struct(t *testing.T) {
|
|||||||
CREATE TABLE %s (
|
CREATE TABLE %s (
|
||||||
id int(11) NOT NULL,
|
id int(11) NOT NULL,
|
||||||
name varchar(45) DEFAULT NULL,
|
name varchar(45) DEFAULT NULL,
|
||||||
create_at datetime DEFAULT NULL,
|
create_at datetime(6) DEFAULT NULL,
|
||||||
update_at datetime DEFAULT NULL,
|
update_at datetime(6) DEFAULT NULL,
|
||||||
delete_at datetime DEFAULT NULL,
|
delete_at datetime(6) DEFAULT NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
`, table)); err != nil {
|
`, table)); err != nil {
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/gogf/gf/v2/util/guid"
|
"github.com/gogf/gf/v2/util/guid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// https://github.com/gogf/gf/issues/1934
|
||||||
func Test_Issue1934(t *testing.T) {
|
func Test_Issue1934(t *testing.T) {
|
||||||
table := createInitTable()
|
table := createInitTable()
|
||||||
defer dropTable(table)
|
defer dropTable(table)
|
||||||
@ -460,12 +461,12 @@ func Test_Issue2105(t *testing.T) {
|
|||||||
|
|
||||||
// https://github.com/gogf/gf/issues/2231
|
// https://github.com/gogf/gf/issues/2231
|
||||||
func Test_Issue2231(t *testing.T) {
|
func Test_Issue2231(t *testing.T) {
|
||||||
linkPattern := `(\w+):([\w\-]*):(.*?)@(\w+?)\((.+?)\)/{0,1}([^\?]*)\?{0,1}(.*)`
|
var (
|
||||||
link := `mysql:root:12345678@tcp(127.0.0.1:3306)/a正bc式?loc=Local&parseTime=true`
|
pattern = `(\w+):([\w\-]*):(.*?)@(\w+?)\((.+?)\)/{0,1}([^\?]*)\?{0,1}(.*)`
|
||||||
|
link = `mysql:root:12345678@tcp(127.0.0.1:3306)/a正bc式?loc=Local&parseTime=true`
|
||||||
|
)
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
match, err := gregex.MatchString(pattern, link)
|
||||||
match, err := gregex.MatchString(linkPattern, link)
|
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
t.Assert(match[1], "mysql")
|
t.Assert(match[1], "mysql")
|
||||||
t.Assert(match[2], "root")
|
t.Assert(match[2], "root")
|
||||||
@ -476,3 +477,190 @@ func Test_Issue2231(t *testing.T) {
|
|||||||
t.Assert(match[7], "loc=Local&parseTime=true")
|
t.Assert(match[7], "loc=Local&parseTime=true")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/gogf/gf/issues/2339
|
||||||
|
func Test_Issue2339(t *testing.T) {
|
||||||
|
table := createInitTable()
|
||||||
|
defer dropTable(table)
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
model1 := db.Model(table, "u1").Where("id between ? and ?", 1, 9)
|
||||||
|
model2 := db.Model("? as u2", model1)
|
||||||
|
model3 := db.Model("? as u3", model2)
|
||||||
|
all2, err := model2.WhereGT("id", 6).OrderAsc("id").All()
|
||||||
|
t.AssertNil(err)
|
||||||
|
t.Assert(len(all2), 3)
|
||||||
|
t.Assert(all2[0]["id"], 7)
|
||||||
|
|
||||||
|
all3, err := model3.WhereGT("id", 7).OrderAsc("id").All()
|
||||||
|
t.AssertNil(err)
|
||||||
|
t.Assert(len(all3), 2)
|
||||||
|
t.Assert(all3[0]["id"], 8)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/gogf/gf/issues/2356
|
||||||
|
func Test_Issue2356(t *testing.T) {
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
table := "demo_" + guid.S()
|
||||||
|
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||||
|
CREATE TABLE %s (
|
||||||
|
id BIGINT(20) UNSIGNED NOT NULL DEFAULT '0',
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
`, table,
|
||||||
|
)); err != nil {
|
||||||
|
t.AssertNil(err)
|
||||||
|
}
|
||||||
|
defer dropTable(table)
|
||||||
|
|
||||||
|
if _, err := db.Exec(ctx, fmt.Sprintf(`INSERT INTO %s (id) VALUES (18446744073709551615);`, table)); err != nil {
|
||||||
|
t.AssertNil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
one, err := db.Model(table).One()
|
||||||
|
t.AssertNil(err)
|
||||||
|
t.AssertEQ(one["id"].Val(), uint64(18446744073709551615))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/gogf/gf/issues/2338
|
||||||
|
func Test_Issue2338(t *testing.T) {
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
table1 := "demo_" + guid.S()
|
||||||
|
table2 := "demo_" + guid.S()
|
||||||
|
if _, err := db.Schema(TestSchema1).Exec(ctx, fmt.Sprintf(`
|
||||||
|
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(6) DEFAULT NULL COMMENT 'Created Time',
|
||||||
|
update_at datetime(6) DEFAULT NULL COMMENT 'Updated Time',
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
`, table1,
|
||||||
|
)); err != nil {
|
||||||
|
t.AssertNil(err)
|
||||||
|
}
|
||||||
|
if _, err := db.Schema(TestSchema2).Exec(ctx, fmt.Sprintf(`
|
||||||
|
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(6) DEFAULT NULL COMMENT 'Created Time',
|
||||||
|
update_at datetime(6) DEFAULT NULL COMMENT 'Updated Time',
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
`, table2,
|
||||||
|
)); err != nil {
|
||||||
|
t.AssertNil(err)
|
||||||
|
}
|
||||||
|
defer dropTableWithDb(db.Schema(TestSchema1), table1)
|
||||||
|
defer dropTableWithDb(db.Schema(TestSchema2), table2)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
_, err = db.Schema(TestSchema1).Model(table1).Insert(g.Map{
|
||||||
|
"id": 1,
|
||||||
|
"nickname": "name_1",
|
||||||
|
})
|
||||||
|
t.AssertNil(err)
|
||||||
|
|
||||||
|
_, err = db.Schema(TestSchema2).Model(table2).Insert(g.Map{
|
||||||
|
"id": 1,
|
||||||
|
"nickname": "name_2",
|
||||||
|
})
|
||||||
|
t.AssertNil(err)
|
||||||
|
|
||||||
|
tableName1 := fmt.Sprintf(`%s.%s`, TestSchema1, table1)
|
||||||
|
tableName2 := fmt.Sprintf(`%s.%s`, TestSchema2, table2)
|
||||||
|
all, err := db.Model(tableName1).As(`a`).
|
||||||
|
LeftJoin(tableName2+" b", `a.id=b.id`).
|
||||||
|
Fields(`a.id`, `b.nickname`).All()
|
||||||
|
t.AssertNil(err)
|
||||||
|
t.Assert(len(all), 1)
|
||||||
|
t.Assert(all[0]["nickname"], "name_2")
|
||||||
|
})
|
||||||
|
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
table1 := "demo_" + guid.S()
|
||||||
|
table2 := "demo_" + guid.S()
|
||||||
|
if _, err := db.Schema(TestSchema1).Exec(ctx, fmt.Sprintf(`
|
||||||
|
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(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,
|
||||||
|
)); err != nil {
|
||||||
|
t.AssertNil(err)
|
||||||
|
}
|
||||||
|
if _, err := db.Schema(TestSchema2).Exec(ctx, fmt.Sprintf(`
|
||||||
|
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(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,
|
||||||
|
)); err != nil {
|
||||||
|
t.AssertNil(err)
|
||||||
|
}
|
||||||
|
defer dropTableWithDb(db.Schema(TestSchema1), table1)
|
||||||
|
defer dropTableWithDb(db.Schema(TestSchema2), table2)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
_, err = db.Schema(TestSchema1).Model(table1).Insert(g.Map{
|
||||||
|
"id": 1,
|
||||||
|
"nickname": "name_1",
|
||||||
|
})
|
||||||
|
t.AssertNil(err)
|
||||||
|
|
||||||
|
_, err = db.Schema(TestSchema2).Model(table2).Insert(g.Map{
|
||||||
|
"id": 1,
|
||||||
|
"nickname": "name_2",
|
||||||
|
})
|
||||||
|
t.AssertNil(err)
|
||||||
|
|
||||||
|
tableName1 := fmt.Sprintf(`%s.%s`, TestSchema1, table1)
|
||||||
|
tableName2 := fmt.Sprintf(`%s.%s`, TestSchema2, table2)
|
||||||
|
all, err := db.Model(tableName1).As(`a`).
|
||||||
|
LeftJoin(tableName2+" b", `a.id=b.id`).
|
||||||
|
Fields(`a.id`, `b.nickname`).All()
|
||||||
|
t.AssertNil(err)
|
||||||
|
t.Assert(len(all), 1)
|
||||||
|
t.Assert(all[0]["nickname"], "name_2")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/gogf/gf/issues/2427
|
||||||
|
func Test_Issue2427(t *testing.T) {
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
table := "demo_" + guid.S()
|
||||||
|
if _, err := db.Exec(ctx, fmt.Sprintf(`
|
||||||
|
CREATE TABLE %s (
|
||||||
|
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
|
||||||
|
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(6) DEFAULT NULL COMMENT 'Created Time',
|
||||||
|
update_at datetime(6) DEFAULT NULL COMMENT 'Updated Time',
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
`, table,
|
||||||
|
)); err != nil {
|
||||||
|
t.AssertNil(err)
|
||||||
|
}
|
||||||
|
defer dropTable(table)
|
||||||
|
|
||||||
|
_, err1 := db.Model(table).Delete()
|
||||||
|
t.Assert(err1, `there should be WHERE condition statement for DELETE operation`)
|
||||||
|
|
||||||
|
_, err2 := db.Model(table).Where(g.Map{}).Delete()
|
||||||
|
t.Assert(err2, `there should be WHERE condition statement for DELETE operation`)
|
||||||
|
|
||||||
|
_, err3 := db.Model(table).Where(1).Delete()
|
||||||
|
t.AssertNil(err3)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -3134,9 +3134,9 @@ func createTableForTimeZoneTest() string {
|
|||||||
passport varchar(45) NULL,
|
passport varchar(45) NULL,
|
||||||
password char(32) NULL,
|
password char(32) NULL,
|
||||||
nickname varchar(45) NULL,
|
nickname varchar(45) NULL,
|
||||||
created_at timestamp NULL,
|
created_at timestamp(6) NULL,
|
||||||
updated_at timestamp NULL,
|
updated_at timestamp(6) NULL,
|
||||||
deleted_at timestamp NULL,
|
deleted_at timestamp(6) NULL,
|
||||||
PRIMARY KEY (id)
|
PRIMARY KEY (id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
`, tableName,
|
`, tableName,
|
||||||
|
|||||||
@ -35,6 +35,10 @@ type Driver struct {
|
|||||||
*gdb.Core
|
*gdb.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
quoteChar = `"`
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := gdb.Register(`oracle`, New()); err != nil {
|
if err := gdb.Register(`oracle`, New()); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -106,7 +110,7 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
|||||||
|
|
||||||
// GetChars returns the security char for this type of database.
|
// GetChars returns the security char for this type of database.
|
||||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||||
return `"`, `"`
|
return quoteChar, quoteChar
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||||
@ -217,7 +221,7 @@ func (d *Driver) TableFields(
|
|||||||
var (
|
var (
|
||||||
result gdb.Result
|
result gdb.Result
|
||||||
link gdb.Link
|
link gdb.Link
|
||||||
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||||
structureSql = fmt.Sprintf(`
|
structureSql = fmt.Sprintf(`
|
||||||
SELECT
|
SELECT
|
||||||
COLUMN_NAME AS FIELD,
|
COLUMN_NAME AS FIELD,
|
||||||
@ -230,7 +234,7 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
|
|||||||
strings.ToUpper(table),
|
strings.ToUpper(table),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||||
|
|||||||
@ -36,6 +36,7 @@ type Driver struct {
|
|||||||
const (
|
const (
|
||||||
internalPrimaryKeyInCtx gctx.StrKey = "primary_key"
|
internalPrimaryKeyInCtx gctx.StrKey = "primary_key"
|
||||||
defaultSchema = "public"
|
defaultSchema = "public"
|
||||||
|
quoteChar = `"`
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -86,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 != "" {
|
if config.Timezone != "" {
|
||||||
source = fmt.Sprintf("%s timezone=%s", source, config.Timezone)
|
source = fmt.Sprintf("%s timezone=%s", source, config.Timezone)
|
||||||
}
|
}
|
||||||
@ -113,7 +118,7 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
|||||||
|
|
||||||
// GetChars returns the security char for this type of database.
|
// GetChars returns the security char for this type of database.
|
||||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||||
return `"`, `"`
|
return quoteChar, quoteChar
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckLocalTypeForField checks and returns corresponding local golang type for given db type.
|
// CheckLocalTypeForField checks and returns corresponding local golang type for given db type.
|
||||||
@ -270,7 +275,7 @@ func (d *Driver) TableFields(ctx context.Context, table string, schema ...string
|
|||||||
var (
|
var (
|
||||||
result gdb.Result
|
result gdb.Result
|
||||||
link gdb.Link
|
link gdb.Link
|
||||||
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||||
structureSql = fmt.Sprintf(`
|
structureSql = fmt.Sprintf(`
|
||||||
SELECT a.attname AS field, t.typname AS type,a.attnotnull as null,
|
SELECT a.attname AS field, t.typname AS type,a.attnotnull as null,
|
||||||
(case when d.contype is not null then 'pri' else '' end) as key
|
(case when d.contype is not null then 'pri' else '' end) as key
|
||||||
@ -288,7 +293,7 @@ ORDER BY a.attnum`,
|
|||||||
table,
|
table,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||||
|
|||||||
@ -33,6 +33,10 @@ type Driver struct {
|
|||||||
*gdb.Core
|
*gdb.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
quoteChar = "`"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := gdb.Register(`sqlite`, New()); err != nil {
|
if err := gdb.Register(`sqlite`, New()); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -105,7 +109,7 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
|
|||||||
|
|
||||||
// GetChars returns the security char for this type of database.
|
// GetChars returns the security char for this type of database.
|
||||||
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
func (d *Driver) GetChars() (charLeft string, charRight string) {
|
||||||
return "`", "`"
|
return quoteChar, quoteChar
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
// DoFilter deals with the sql string before commits it to underlying sql driver.
|
||||||
@ -141,14 +145,14 @@ func (d *Driver) TableFields(
|
|||||||
ctx context.Context, table string, schema ...string,
|
ctx context.Context, table string, schema ...string,
|
||||||
) (fields map[string]*gdb.TableField, err error) {
|
) (fields map[string]*gdb.TableField, err error) {
|
||||||
var (
|
var (
|
||||||
result gdb.Result
|
result gdb.Result
|
||||||
link gdb.Link
|
link gdb.Link
|
||||||
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
|
||||||
)
|
)
|
||||||
if link, err = d.SlaveLink(useSchema); err != nil {
|
if link, err = d.SlaveLink(usedSchema); err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,15 +33,16 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TableSize = 10
|
TableSize = 10
|
||||||
TableName = "user"
|
TableName = "user"
|
||||||
TestSchema1 = "test1"
|
TableNameWhichIsKeyword = "group"
|
||||||
TestSchema2 = "test2"
|
TestSchema1 = "test1"
|
||||||
TableNamePrefix = "gf_"
|
TestSchema2 = "test2"
|
||||||
CreateTime = "2018-10-24 10:00:00"
|
TableNamePrefix = "gf_"
|
||||||
DBGroupTest = "test"
|
CreateTime = "2018-10-24 10:00:00"
|
||||||
DBGroupPrefix = "prefix"
|
DBGroupTest = "test"
|
||||||
DBGroupInvalid = "invalid"
|
DBGroupPrefix = "prefix"
|
||||||
|
DBGroupInvalid = "invalid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -125,7 +126,7 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
|
|||||||
nickname VARCHAR(45),
|
nickname VARCHAR(45),
|
||||||
create_time DATETIME
|
create_time DATETIME
|
||||||
);
|
);
|
||||||
`, name,
|
`, db.GetCore().QuoteWord(name),
|
||||||
)); err != nil {
|
)); err != nil {
|
||||||
gtest.Fatal(err)
|
gtest.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,6 +38,27 @@ func Test_New(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_New_Path_With_Colon(t *testing.T) {
|
||||||
|
gtest.C(t, func(t *gtest.T) {
|
||||||
|
|
||||||
|
dbFilePathWithColon := gfile.Join(dbDir, "test:1")
|
||||||
|
if err := gfile.Mkdir(dbFilePathWithColon); err != nil {
|
||||||
|
gtest.Error(err)
|
||||||
|
}
|
||||||
|
node := gdb.ConfigNode{
|
||||||
|
Type: "sqlite",
|
||||||
|
Link: fmt.Sprintf(`sqlite::@file(%s)`, gfile.Join(dbFilePathWithColon, "test.db")),
|
||||||
|
Charset: "utf8",
|
||||||
|
}
|
||||||
|
newDb, err := gdb.New(node)
|
||||||
|
t.AssertNil(err)
|
||||||
|
value, err := newDb.GetValue(ctx, `select 1`)
|
||||||
|
t.AssertNil(err)
|
||||||
|
t.Assert(value, `1`)
|
||||||
|
t.AssertNil(newDb.Close(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func Test_DB_Ping(t *testing.T) {
|
func Test_DB_Ping(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
err1 := db.PingMaster()
|
err1 := db.PingMaster()
|
||||||
@ -1550,3 +1571,39 @@ func Test_TableFields(t *testing.T) {
|
|||||||
gtest.AssertNE(err, nil)
|
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"])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -47,6 +47,7 @@ func New(config *gredis.Config) *Redis {
|
|||||||
Password: config.Pass,
|
Password: config.Pass,
|
||||||
DB: config.Db,
|
DB: config.Db,
|
||||||
MaxRetries: defaultMaxRetries,
|
MaxRetries: defaultMaxRetries,
|
||||||
|
PoolSize: config.MaxActive,
|
||||||
MinIdleConns: config.MinIdle,
|
MinIdleConns: config.MinIdle,
|
||||||
MaxConnAge: config.MaxConnLifetime,
|
MaxConnAge: config.MaxConnLifetime,
|
||||||
IdleTimeout: config.IdleTimeout,
|
IdleTimeout: config.IdleTimeout,
|
||||||
|
|||||||
@ -118,10 +118,10 @@ func (r GroupList) LPop(ctx context.Context, key string, count ...int) (*gvar.Va
|
|||||||
// depending on the list's length.
|
// depending on the list's length.
|
||||||
//
|
//
|
||||||
// It returns:
|
// It returns:
|
||||||
// - When called without the count argument:
|
// - When called without the count argument:
|
||||||
// the value of the last element, or nil when key does not exist.
|
// the value of the last element, or nil when key does not exist.
|
||||||
// - When called with the count argument:
|
// - When called with the count argument:
|
||||||
// list of popped elements, or nil when key does not exist.
|
// list of popped elements, or nil when key does not exist.
|
||||||
//
|
//
|
||||||
// https://redis.io/commands/rpop
|
// https://redis.io/commands/rpop
|
||||||
func (r GroupList) RPop(ctx context.Context, key string, count ...int) (*gvar.Var, error) {
|
func (r GroupList) RPop(ctx context.Context, key string, count ...int) (*gvar.Var, error) {
|
||||||
|
|||||||
@ -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.
|
// argument, the reply will consist of up to count members, depending on the set's cardinality.
|
||||||
//
|
//
|
||||||
// It returns:
|
// It returns:
|
||||||
// - When called without the count argument:
|
// - When called without the count argument:
|
||||||
// Bulk string reply: the removed member, or nil when key does not exist.
|
// Bulk string reply: the removed member, or nil when key does not exist.
|
||||||
// - When called with the count argument:
|
// - When called with the count argument:
|
||||||
// Array reply: the removed members, or an empty array when key does not exist.
|
// Array reply: the removed members, or an empty array when key does not exist.
|
||||||
//
|
//
|
||||||
// https://redis.io/commands/spop/
|
// https://redis.io/commands/spop/
|
||||||
func (r GroupSet) SPop(ctx context.Context, key string, count ...int) (*gvar.Var, error) {
|
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.
|
// of the specified count.
|
||||||
//
|
//
|
||||||
// It returns:
|
// It returns:
|
||||||
// - Bulk string reply: without the additional count argument, the command returns a Bulk Reply with the
|
// - 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.
|
// 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,
|
// - 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.
|
// or an empty array when key does not exist.
|
||||||
//
|
//
|
||||||
// https://redis.io/commands/srandmember/
|
// https://redis.io/commands/srandmember/
|
||||||
func (r GroupSet) SRandMember(ctx context.Context, key string, count ...int) (*gvar.Var, error) {
|
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.
|
// 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.
|
// It returns the number of elements in the resulting set.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -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.
|
// 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:
|
// This command is equivalent to executing the following commands:
|
||||||
//
|
//
|
||||||
// SET myKey value
|
// SET myKey value
|
||||||
// EXPIRE myKey seconds
|
// EXPIRE myKey seconds
|
||||||
//
|
//
|
||||||
// SetEX is atomic, and can be reproduced by using the previous two commands inside an MULTI / EXEC block.
|
// 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
|
// It is provided as a faster alternative to the given sequence of operations, because this operation is very
|
||||||
|
|||||||
@ -246,7 +246,7 @@ func Test_AdapterRedis_SetIfNotExistFunc(t *testing.T) {
|
|||||||
return 11, nil
|
return 11, nil
|
||||||
}, 0)
|
}, 0)
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
t.Assert(exist, false)
|
t.Assert(exist, true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +257,7 @@ func Test_AdapterRedis_SetIfNotExistFuncLock(t *testing.T) {
|
|||||||
return 11, nil
|
return 11, nil
|
||||||
}, 0)
|
}, 0)
|
||||||
t.AssertNil(err)
|
t.AssertNil(err)
|
||||||
t.Assert(exist, false)
|
t.Assert(exist, true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
3
contrib/registry/README.MD
Normal file
3
contrib/registry/README.MD
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Service registrar and discovery
|
||||||
|
|
||||||
|
Please refer to certain sub folder.
|
||||||
@ -8,16 +8,16 @@
|
|||||||
package etcd
|
package etcd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
etcd3 "go.etcd.io/etcd/client/v3"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/errors/gcode"
|
"github.com/gogf/gf/v2/errors/gcode"
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/net/gsvc"
|
"github.com/gogf/gf/v2/net/gsvc"
|
||||||
"github.com/gogf/gf/v2/os/glog"
|
"github.com/gogf/gf/v2/os/glog"
|
||||||
"github.com/gogf/gf/v2/text/gstr"
|
"github.com/gogf/gf/v2/text/gstr"
|
||||||
etcd3 "go.etcd.io/etcd/client/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -45,7 +45,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// New creates and returns a new etcd registry.
|
// 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, ",")
|
endpoints := gstr.SplitAndTrim(address, ",")
|
||||||
if len(endpoints) == 0 {
|
if len(endpoints) == 0 {
|
||||||
panic(gerror.NewCodef(gcode.CodeInvalidParameter, `invalid etcd address "%s"`, address))
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
services []gsvc.Service
|
services []gsvc.Service
|
||||||
serviceKey string
|
servicePrefixMap = make(map[string]*Service)
|
||||||
serviceMap = make(map[string]*gsvc.LocalService)
|
|
||||||
)
|
)
|
||||||
for _, kv := range res.Kvs {
|
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 {
|
if err != nil {
|
||||||
return services, err
|
return services, err
|
||||||
}
|
}
|
||||||
localService, ok := service.(*gsvc.LocalService)
|
s := NewService(service)
|
||||||
if !ok {
|
if v, ok := servicePrefixMap[service.GetPrefix()]; ok {
|
||||||
return nil, gerror.Newf(
|
v.Endpoints = append(v.Endpoints, service.GetEndpoints()...)
|
||||||
`service from "gsvc.NewServiceWithKV" is not "*gsvc.LocalService", but "%s"`,
|
} else {
|
||||||
reflect.TypeOf(service),
|
servicePrefixMap[s.GetPrefix()] = s
|
||||||
)
|
services = append(services, s)
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return services, nil
|
return services, nil
|
||||||
|
|||||||
@ -9,11 +9,14 @@ package etcd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/net/gsvc"
|
|
||||||
etcd3 "go.etcd.io/etcd/client/v3"
|
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) {
|
func (r *Registry) Search(ctx context.Context, in gsvc.SearchInput) ([]gsvc.Service, error) {
|
||||||
if in.Prefix == "" && in.Name != "" {
|
if in.Prefix == "" && in.Name != "" {
|
||||||
in.Prefix = gsvc.NewServiceWithName(in.Name).GetPrefix()
|
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.
|
// Service filter.
|
||||||
filteredServices := make([]gsvc.Service, 0)
|
filteredServices := make([]gsvc.Service, 0)
|
||||||
for _, v := range services {
|
for _, service := range services {
|
||||||
if in.Name != "" && in.Name != v.GetName() {
|
if in.Prefix != "" && !gstr.HasPrefix(service.GetKey(), in.Prefix) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if in.Version != "" && in.Version != v.GetVersion() {
|
if in.Name != "" && service.GetName() != in.Name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
service := v
|
if in.Version != "" && service.GetVersion() != in.Version {
|
||||||
filteredServices = append(filteredServices, service)
|
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
|
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) {
|
func (r *Registry) Watch(ctx context.Context, key string) (gsvc.Watcher, error) {
|
||||||
return newWatcher(key, r.client)
|
return newWatcher(key, r.client)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,13 +9,16 @@ package etcd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
etcd3 "go.etcd.io/etcd/client/v3"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
"github.com/gogf/gf/v2/net/gsvc"
|
"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) {
|
func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Service, error) {
|
||||||
|
service = NewService(service)
|
||||||
r.lease = etcd3.NewLease(r.client)
|
r.lease = etcd3.NewLease(r.client)
|
||||||
grant, err := r.lease.Grant(ctx, int64(r.keepaliveTTL.Seconds()))
|
grant, err := r.lease.Grant(ctx, int64(r.keepaliveTTL.Seconds()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -33,7 +36,7 @@ func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Ser
|
|||||||
key, value, grant.ID,
|
key, value, grant.ID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
r.logger.Infof(
|
r.logger.Debugf(
|
||||||
ctx,
|
ctx,
|
||||||
`etcd put success with key "%s", value "%s", lease "%d"`,
|
`etcd put success with key "%s", value "%s", lease "%d"`,
|
||||||
key, value, grant.ID,
|
key, value, grant.ID,
|
||||||
@ -46,7 +49,7 @@ func (r *Registry) Register(ctx context.Context, service gsvc.Service) (gsvc.Ser
|
|||||||
return service, nil
|
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 {
|
func (r *Registry) Deregister(ctx context.Context, service gsvc.Service) error {
|
||||||
_, err := r.client.Delete(ctx, service.GetKey())
|
_, err := r.client.Delete(ctx, service.GetKey())
|
||||||
if r.lease != nil {
|
if r.lease != nil {
|
||||||
|
|||||||
64
contrib/registry/etcd/etcd_service.go
Normal file
64
contrib/registry/etcd/etcd_service.go
Normal 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)
|
||||||
|
}
|
||||||
@ -9,8 +9,9 @@ package etcd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/net/gsvc"
|
|
||||||
etcd3 "go.etcd.io/etcd/client/v3"
|
etcd3 "go.etcd.io/etcd/client/v3"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/net/gsvc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -46,6 +47,7 @@ func (w *watcher) Proceed() ([]gsvc.Service, error) {
|
|||||||
case <-w.ctx.Done():
|
case <-w.ctx.Done():
|
||||||
return nil, w.ctx.Err()
|
return nil, w.ctx.Err()
|
||||||
case <-w.watchChan:
|
case <-w.watchChan:
|
||||||
|
// It retrieves, merges and returns all services by prefix if any changes.
|
||||||
return w.getServicesByPrefix()
|
return w.getServicesByPrefix()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
145
contrib/registry/etcd/etcd_z_test.go
Normal file
145
contrib/registry/etcd/etcd_z_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
77
contrib/registry/file/README.MD
Normal file
77
contrib/registry/file/README.MD
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
42
contrib/registry/file/file.go
Normal file
42
contrib/registry/file/file.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
134
contrib/registry/file/file_discovery.go
Normal file
134
contrib/registry/file/file_discovery.go
Normal 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
|
||||||
|
}
|
||||||
62
contrib/registry/file/file_registrar.go
Normal file
62
contrib/registry/file/file_registrar.go
Normal 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
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user