Compare commits

...

55 Commits

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

* version updates
2023-03-07 19:32:13 +08:00
e8051bad9a fix issue in empty json name along with omitempty tag in package oai (#2500) 2023-03-07 14:17:14 +08:00
d0d41a63a6 robot translator updates (#2491) 2023-03-07 11:01:01 +08:00
853b038a47 add issue translation robot (#2490) 2023-03-06 09:50:03 +08:00
34946f6105 fix configuration of Namespace for pgsql (#2481) 2023-03-01 19:30:40 +08:00
Gin
15d88c269d fix: gredis maxActive config and duplicate connection bug (#2479) 2023-02-27 22:08:37 +08:00
cbbfd85eeb Protect RemoveValue from race condition (#2472) 2023-02-23 19:47:57 +08:00
adf90c876f improve unit testing cases (#2468) 2023-02-23 10:07:40 +08:00
b4f76b8448 fix: use keyword(like: group) as table name in sqlite (#2461) 2023-02-20 22:03:26 +08:00
ed858ebd4b Bump golang.org/x/net from 0.1.0 to 0.7.0 in /cmd/gf (#2460) 2023-02-20 22:02:31 +08:00
272b9c7afd README update for cmd/gf (#2459) 2023-02-20 22:01:36 +08:00
8dc8dd9756 fix issue #2261 (#2458) 2023-02-20 21:57:49 +08:00
a64d1001e2 improve ci by using cache feature of setup-go (#2463) 2023-02-20 21:51:54 +08:00
ad737ded3c fix issue #2447 (#2448) 2023-02-15 14:13:32 +08:00
ac6b0c0980 fix issue #2427 (#2442) 2023-02-15 09:45:40 +08:00
b69e0ff9f7 fix issue #2338 (#2444) 2023-02-14 09:45:29 +08:00
0361f9f7de fix #2435 (#2437) 2023-02-13 19:18:30 +08:00
005668aca8 gdb error should wrap original underlying database error like MySQLError (#2402) 2023-02-08 19:38:11 +08:00
013f8b216a improve Timezone escape for driver dm/mysql (#2412) 2023-02-08 19:35:48 +08:00
8ecfa91e5d comment updates for with function of package gdb (#2418) 2023-02-08 19:10:03 +08:00
117fc6eda2 fix issue #2339 (#2433) 2023-02-08 19:08:10 +08:00
d66af122c7 fix issue #2331 (#2432) 2023-02-08 19:07:05 +08:00
a7467945ca fix issue #2355 (#2430) 2023-02-08 14:17:21 +08:00
81d8aa55cd fix issue #2371 (#2429) 2023-02-08 14:17:11 +08:00
4a6630138d fix issue 2356 (#2428) 2023-02-08 14:17:00 +08:00
3adae3a9aa fix type of default value in swagger ui for package goai (#2413) 2023-02-08 14:16:12 +08:00
21ebf48072 .gitignore updates (#2426) 2023-02-07 21:13:20 +08:00
2b90bcfab6 fix issue #2050: add -t option support for command gf docker to compatable with older version (#2423) 2023-02-07 17:41:43 +08:00
5f0641f348 fix issue #2015 (#2422) 2023-02-07 14:06:26 +08:00
38c9cac578 fix issue #2011 (#2421) 2023-02-07 11:37:39 +08:00
9ba49fa454 fix issue in gf run failed with arguments passed in windows platform (#2414) 2023-02-06 20:35:11 +08:00
39fede66e6 add label planned for ci to check issue inactive (#2408)
add label planned for ci to check issue inactive
2023-01-18 17:04:26 +08:00
d984f1a9d8 add auto go mod tidy after version upgraded for command up (#2407)
* add cli upgraded supported for command up

* improve unit case for package internal/mutex

* v2.3.1

* add auto  after version upgraded for command

Co-authored-by: houseme <housemecn@gmail.com>
2023-01-18 11:28:55 +08:00
Gin
28b8efe00c fix issue 2403 (#2404) 2023-01-18 10:17:16 +08:00
7b0fd6de9b add cli upgraded supported for command up (#2405)
* add cli upgraded supported for command up

* improve unit case for package internal/mutex

* v2.3.1

Co-authored-by: houseme <housemecn@gmail.com>
2023-01-18 10:12:00 +08:00
c0fa2e3a73 fix issue 2395 (#2399)
* v2.3.0

* fix #2391

* fix issue #2391

* fix issue #2395
2023-01-17 14:51:19 +08:00
3f6669e2b7 fix issue 2391 (#2398)
* v2.3.0

* fix #2391

* fix issue #2391
2023-01-16 16:00:18 +08:00
304 changed files with 10712 additions and 2935 deletions

View File

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

View File

@ -25,4 +25,4 @@ jobs:
inactive-label: 'inactive'
inactive-day: 7
issue-state: open
exclude-labels: 'bug,$exclude-empty'
exclude-labels: 'bug,planned,$exclude-empty'

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

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

3
.gitignore vendored
View File

@ -7,11 +7,8 @@
.settings/
.vscode/
vendor/
composer.lock
gitpush.sh
pkg/
bin/
cbuild
**/.DS_Store
.test/
cmd/gf/main

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 -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 = `
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`
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`
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`
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"`
@ -61,6 +63,7 @@ func init() {
`cDockerShellBrief`: cDockerShellBrief,
`cDockerBuildBrief`: cDockerBuildBrief,
`cDockerPushBrief`: cDockerPushBrief,
`cDockerTagBrief`: cDockerTagBrief,
`cDockerTagNameBrief`: cDockerTagNameBrief,
`cDockerTagPrefixesBrief`: cDockerTagPrefixesBrief,
`cDockerExtraBrief`: cDockerExtraBrief,
@ -73,6 +76,7 @@ type cDockerInput struct {
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"`
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"`
TagPrefixes []string `name:"tagPrefixes" short:"tp" brief:"{cDockerTagPrefixesBrief}" v:"required-with:TagName"`
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 {
dockerTags = []string{""}
dockerTags = []string{in.Tag}
}
for i, dockerTag := range dockerTags {
if i > 0 {

View File

@ -2,6 +2,7 @@ package cmd
import (
"context"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/errors/gerror"
@ -19,8 +20,9 @@ type cFix struct {
}
type cFixInput struct {
g.Meta `name:"fix"`
Path string `name:"path" brief:"directory path, it uses current working directory in default"`
g.Meta `name:"fix"`
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{}
@ -31,38 +33,45 @@ type cFixItem struct {
}
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 == "" {
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)
return
}
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{
{Version: "v2.3", Func: c.doFixV23},
}
for _, item := range items {
if gstr.CompareVersionGo(version, item.Version) < 0 {
if gstr.CompareVersionGo(in.Version, item.Version) < 0 {
mlog.Debugf(
`current GoFrame version "%s" is lesser than "%s", nothing to do`,
version, item.Version,
`current GoFrame or contrib package version "%s" is lesser than "%s", nothing to do`,
in.Version, item.Version,
)
continue
}
if err = item.Func(version); err != nil {
if err = item.Func(in.Version); err != nil {
return
}
}
@ -87,7 +96,7 @@ func (c cFix) doFixV23(version string) error {
return gfile.ReplaceDirFunc(replaceFunc, ".", "*.go", true)
}
func (c cFix) getVersion(in cFixInput) (string, error) {
func (c cFix) autoDetectVersion(in cFixInput) (string, error) {
var (
err error
path = gfile.Join(in.Path, "go.mod")

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"runtime"
"strings"
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/frame/g"
@ -154,7 +155,7 @@ func (app *cRunApp) Run(ctx context.Context) {
if runtime.GOOS == "windows" {
// Special handling for windows platform.
// DO NOT USE "cmd /c" command.
process = gproc.NewProcess(runCommand, nil)
process = gproc.NewProcess(outputPath, strings.Fields(app.Args))
} else {
process = gproc.NewProcessCmd(runCommand, nil)
}

View File

@ -3,6 +3,9 @@ package cmd
import (
"context"
"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/v2/frame/g"
@ -26,7 +29,7 @@ const (
gf up
gf up -a
gf up -c
gf up -f -c
gf up -cf
`
)
@ -39,8 +42,8 @@ func init() {
type cUpInput struct {
g.Meta `name:"up" config:"gfcli.up"`
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 (not supported yet)" orphan:"true"`
Cli bool `name:"cli" short:"c" brief:"also upgrade CLI tool" 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{}
@ -48,42 +51,72 @@ type cUpOutput struct{}
func (c cUp) Index(ctx context.Context, in cUpInput) (out *cUpOutput, err error) {
defer func() {
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 {
in.Cli = true
in.Fix = true
}
if err = c.doUpgradeVersion(ctx, in); err != nil {
if doUpgradeVersionOut, err = c.doUpgradeVersion(ctx, in); err != nil {
return nil, err
}
//if in.Cli {
// if err = c.doUpgradeCLI(ctx); err != nil {
// return nil, err
// }
//}
if in.Cli {
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
}
func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (err error) {
mlog.Print(`start upgrading version...`)
type doUpgradeVersionOutput struct {
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 {
Name string
Version string
}
var (
dir = gfile.Pwd()
temp string
path = gfile.Join(dir, "go.mod")
temp string
dirPath = gfile.Pwd()
goModPath = gfile.Join(dirPath, "go.mod")
)
// It recursively upgrades the go.mod from sub folder to its parent folders.
for {
if gfile.Exists(path) {
if gfile.Exists(goModPath) {
var packages []Package
err = gfile.ReadLines(path, func(line string) error {
err = gfile.ReadLines(goModPath, func(line string) error {
line = gstr.Trim(line)
if gstr.HasPrefix(line, gfPackage) {
array := gstr.SplitAndTrim(line, " ")
@ -99,38 +132,76 @@ func (c cUp) doUpgradeVersion(ctx context.Context, in cUpInput) (err error) {
}
for _, pkg := range packages {
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 {
return
}
mlog.Print()
}
if in.Fix {
if err = c.doAutoFixing(ctx, dir); err != nil {
return err
// go mod tidy
if err = utils.GoModTidy(ctx, dirPath); err != nil {
return nil, err
}
mlog.Print()
out.Items = append(out.Items, doUpgradeVersionOutputItem{
DirPath: dirPath,
Version: pkg.Version,
})
}
return
}
temp = gfile.Dir(dir)
if temp == "" || temp == dir {
temp = gfile.Dir(dirPath)
if temp == "" || temp == dirPath {
return
}
dir = temp
path = gfile.Join(dir, "go.mod")
dirPath = temp
goModPath = gfile.Join(dirPath, "go.mod")
}
}
// doUpgradeCLI downloads the new version binary with process.
func (c cUp) doUpgradeCLI(ctx context.Context) (err error) {
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
}
func (c cUp) doAutoFixing(ctx context.Context, dirPath string) (err error) {
mlog.Printf(`auto fixing path "%s"...`, dirPath)
err = cFix{}.doFix(cFixInput{
Path: dirPath,
})
func (c cUp) doAutoFixing(ctx context.Context, dirPath string, version string) (err error) {
mlog.Printf(`auto fixing directory path "%s" from version "%s" ...`, dirPath, version)
command := fmt.Sprintf(`gf fix -p %s`, dirPath)
_ = gproc.ShellRun(ctx, command)
return
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

@ -1,12 +1,15 @@
package utils
import (
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
"context"
"fmt"
"golang.org/x/tools/imports"
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/text/gstr"
)
// 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.
func IsFileDoNotEdit(filePath string) bool {
if !gfile.Exists(filePath) {
@ -43,3 +53,16 @@ func IsFileDoNotEdit(filePath string) bool {
}
return gstr.Contains(gfile.GetContents(filePath), consts.DoNotEditKey)
}
// ReplaceGeneratedContentGFV2 replaces generated go content from goframe v1 to v2.
func ReplaceGeneratedContentGFV2(folderPath string) (err error) {
return gfile.ReplaceDirFunc(func(path, content string) string {
if gstr.Contains(content, `"github.com/gogf/gf`) && !gstr.Contains(content, `"github.com/gogf/gf/v2`) {
content = gstr.Replace(content, `"github.com/gogf/gf"`, `"github.com/gogf/gf/v2"`)
content = gstr.Replace(content, `"github.com/gogf/gf/`, `"github.com/gogf/gf/v2/`)
content = gstr.Replace(content, `"github.com/gogf/gf/v2/contrib/`, `"github.com/gogf/gf/contrib/`)
return content
}
return content
}, folderPath, "*.go", true)
}

View File

@ -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)
}
}

View File

@ -173,28 +173,28 @@ func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
return a
}
// InsertBefore inserts the `value` to the front of `index`.
func (a *Array) InsertBefore(index int, value interface{}) error {
// InsertBefore inserts the `values` to the front of `index`.
func (a *Array) InsertBefore(index int, values ...interface{}) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || 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:]...)
a.array = append(a.array[0:index], value)
a.array = append(a.array[0:index], values...)
a.array = append(a.array, rear...)
return nil
}
// InsertAfter inserts the `value` to the back of `index`.
func (a *Array) InsertAfter(index int, value interface{}) error {
// InsertAfter inserts the `values` to the back of `index`.
func (a *Array) InsertAfter(index int, values ...interface{}) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || 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:]...)
a.array = append(a.array[0:index+1], value)
a.array = append(a.array[0:index+1], values...)
a.array = append(a.array, rear...)
return nil
}

View File

@ -168,28 +168,28 @@ func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
return a
}
// InsertBefore inserts the `value` to the front of `index`.
func (a *IntArray) InsertBefore(index int, value int) error {
// InsertBefore inserts the `values` to the front of `index`.
func (a *IntArray) InsertBefore(index int, values ...int) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || 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:]...)
a.array = append(a.array[0:index], value)
a.array = append(a.array[0:index], values...)
a.array = append(a.array, rear...)
return nil
}
// 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()
defer a.mu.Unlock()
if index < 0 || 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:]...)
a.array = append(a.array[0:index+1], value)
a.array = append(a.array[0:index+1], values...)
a.array = append(a.array, rear...)
return nil
}

View File

@ -155,28 +155,28 @@ func (a *StrArray) SortFunc(less func(v1, v2 string) bool) *StrArray {
return a
}
// InsertBefore inserts the `value` to the front of `index`.
func (a *StrArray) InsertBefore(index int, value string) error {
// InsertBefore inserts the `values` to the front of `index`.
func (a *StrArray) InsertBefore(index int, values ...string) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || 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:]...)
a.array = append(a.array[0:index], value)
a.array = append(a.array[0:index], values...)
a.array = append(a.array, rear...)
return nil
}
// InsertAfter inserts the `value` to the back of `index`.
func (a *StrArray) InsertAfter(index int, value string) error {
// InsertAfter inserts the `values` to the back of `index`.
func (a *StrArray) InsertAfter(index int, values ...string) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || 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:]...)
a.array = append(a.array[0:index+1], value)
a.array = append(a.array[0:index+1], values...)
a.array = append(a.array, rear...)
return nil
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -85,7 +85,9 @@ func (q *Queue) Pop() interface{} {
// Notice: It would notify all goroutines return immediately,
// which are being blocked reading using Pop method.
func (q *Queue) Close() {
q.closed.Set(true)
if !q.closed.Cas(false, true) {
return
}
if q.events != nil {
close(q.events)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -143,11 +143,11 @@ func (d *Driver) TableFields(
ctx context.Context, table string, schema ...string,
) (fields map[string]*gdb.TableField, err error) {
var (
result gdb.Result
link gdb.Link
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
result gdb.Result
link gdb.Link
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
}
var (

View File

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

View File

@ -23,6 +23,7 @@ import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gutil"
)
@ -30,6 +31,10 @@ type Driver struct {
*gdb.Core
}
const (
quoteChar = `"`
)
func init() {
var (
err error
@ -72,7 +77,10 @@ func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) {
// Demo of timezone setting:
// &loc=Asia/Shanghai
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 != "" {
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) {
return `"`, `"`
return quoteChar, quoteChar
}
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
newSql, err = gregex.ReplaceString(`["\n\t]`, "", sql)
newSql = gstr.ReplaceI(newSql, "GROUP_CONCAT", "WM_CONCAT")
// g.Dump("Driver.DoFilter()::newSql", newSql)
// gutil.Dump("Driver.DoFilter()::newSql", newSql)
newArgs = args
// g.Dump("Driver.DoFilter()::newArgs", newArgs)
// gutil.Dump("Driver.DoFilter()::newArgs", newArgs)
return
}
@ -276,23 +284,7 @@ func parseValue(listOne gdb.Map, char struct {
)
}
va := reflect.ValueOf(listOne[column])
ty := reflect.TypeOf(listOne[column])
saveValue := ""
switch ty.Kind() {
case reflect.String:
saveValue = va.String()
case reflect.Int:
saveValue = strconv.FormatInt(va.Int(), 10)
case reflect.Int64:
saveValue = strconv.FormatInt(va.Int(), 10)
default:
// The fish has no chance getting here.
// Nothing to do.
}
saveValue := gconv.String(listOne[column])
queryValues = append(
queryValues,
fmt.Sprintf(

View File

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

View File

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

View File

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

View File

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

View File

@ -33,6 +33,10 @@ type Driver struct {
*gdb.Core
}
const (
quoteChar = `"`
)
func init() {
if err := gdb.Register(`mssql`, New()); err != nil {
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.
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.
@ -237,11 +241,11 @@ func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string,
// Also see DriverMysql.TableFields.
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
var (
result gdb.Result
link gdb.Link
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
result gdb.Result
link gdb.Link
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
}
structureSql := fmt.Sprintf(`

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@ import (
"database/sql"
"fmt"
"net/url"
"strings"
_ "github.com/go-sql-driver/mysql"
"github.com/gogf/gf/v2/database/gdb"
@ -27,6 +28,10 @@ type Driver struct {
*gdb.Core
}
const (
quoteChar = "`"
)
func init() {
var (
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,
)
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 != "" {
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.
func (d *Driver) GetChars() (charLeft string, charRight string) {
return "`", "`"
return quoteChar, quoteChar
}
// DoFilter handles the sql before posts it to database.
@ -138,11 +146,11 @@ func (d *Driver) TableFields(
ctx context.Context, table string, schema ...string,
) (fields map[string]*gdb.TableField, err error) {
var (
result gdb.Result
link gdb.Link
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
result gdb.Result
link gdb.Link
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
}
result, err = d.DoSelect(

View File

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

View File

@ -9,8 +9,11 @@ package mysql_test
import (
"testing"
"github.com/gogf/gf/v2/encoding/gjson"
"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/util/gmeta"
)
func Test_Model_Builder(t *testing.T) {
@ -59,4 +62,66 @@ func Test_Model_Builder(t *testing.T) {
t.AssertNil(err)
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})
})
}

View File

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

View File

@ -21,6 +21,7 @@ import (
"github.com/gogf/gf/v2/util/guid"
)
// https://github.com/gogf/gf/issues/1934
func Test_Issue1934(t *testing.T) {
table := createInitTable()
defer dropTable(table)
@ -460,12 +461,12 @@ func Test_Issue2105(t *testing.T) {
// https://github.com/gogf/gf/issues/2231
func Test_Issue2231(t *testing.T) {
linkPattern := `(\w+):([\w\-]*):(.*?)@(\w+?)\((.+?)\)/{0,1}([^\?]*)\?{0,1}(.*)`
link := `mysql:root:12345678@tcp(127.0.0.1:3306)/a正bc式?loc=Local&parseTime=true`
var (
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) {
match, err := gregex.MatchString(linkPattern, link)
match, err := gregex.MatchString(pattern, link)
t.AssertNil(err)
t.Assert(match[1], "mysql")
t.Assert(match[2], "root")
@ -476,3 +477,190 @@ func Test_Issue2231(t *testing.T) {
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)
})
}

View File

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

View File

@ -35,6 +35,10 @@ type Driver struct {
*gdb.Core
}
const (
quoteChar = `"`
)
func init() {
if err := gdb.Register(`oracle`, New()); err != nil {
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.
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.
@ -217,7 +221,7 @@ func (d *Driver) TableFields(
var (
result gdb.Result
link gdb.Link
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
structureSql = fmt.Sprintf(`
SELECT
COLUMN_NAME AS FIELD,
@ -230,7 +234,7 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
strings.ToUpper(table),
)
)
if link, err = d.SlaveLink(useSchema); err != nil {
if link, err = d.SlaveLink(usedSchema); err != nil {
return nil, err
}
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))

View File

@ -36,6 +36,7 @@ type Driver struct {
const (
internalPrimaryKeyInCtx gctx.StrKey = "primary_key"
defaultSchema = "public"
quoteChar = `"`
)
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 != "" {
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.
func (d *Driver) GetChars() (charLeft string, charRight string) {
return `"`, `"`
return quoteChar, quoteChar
}
// 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 (
result gdb.Result
link gdb.Link
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
structureSql = fmt.Sprintf(`
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
@ -288,7 +293,7 @@ ORDER BY a.attnum`,
table,
)
)
if link, err = d.SlaveLink(useSchema); err != nil {
if link, err = d.SlaveLink(usedSchema); err != nil {
return nil, err
}
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))

View File

@ -33,6 +33,10 @@ type Driver struct {
*gdb.Core
}
const (
quoteChar = "`"
)
func init() {
if err := gdb.Register(`sqlite`, New()); err != nil {
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.
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.
@ -141,14 +145,14 @@ func (d *Driver) TableFields(
ctx context.Context, table string, schema ...string,
) (fields map[string]*gdb.TableField, err error) {
var (
result gdb.Result
link gdb.Link
useSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
result gdb.Result
link gdb.Link
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
}
result, err = d.DoSelect(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
result, err = d.DoSelect(ctx, link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, d.QuoteWord(table)))
if err != nil {
return nil, err
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -246,7 +246,7 @@ func Test_AdapterRedis_SetIfNotExistFunc(t *testing.T) {
return 11, nil
}, 0)
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
}, 0)
t.AssertNil(err)
t.Assert(exist, false)
t.Assert(exist, true)
})
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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