From 70b73b2974af94c657a71473b29e4718a777ab62 Mon Sep 17 00:00:00 2001 From: k0a1a Date: Thu, 24 Feb 2022 22:24:52 +0800 Subject: [PATCH 01/96] fix: init config failed --- cmd/init.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/init.go b/cmd/init.go index 6bcb5e9e..e160524d 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -301,7 +301,8 @@ func makeFile(name string, source string) { makeFileContent(name, []byte(source)) } -func makeFileContent(filename string, content []byte) { +func makeFileContent(name string, content []byte) { + filename := filepath.Join(config.Conf.Root, name) err := os.WriteFile(filename, content, 0644) if err != nil { fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) From 73295cfabdbbbe32f1fe3fb1ed7ccbfabaab7901 Mon Sep 17 00:00:00 2001 From: k0a1a Date: Fri, 25 Feb 2022 10:39:09 +0800 Subject: [PATCH 02/96] fix: init config failed --- cmd/root.go | 44 ++++++++++++++------------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index fc40b6f0..2bbc9d7b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -105,42 +105,26 @@ func Execute() { } } -// Boot 设定配置 (这个逻辑有 BUG ) +// Boot 设定配置 func Boot() { - - if envFile == "" && appPath != "" { - root, err := filepath.Abs(appPath) + root := config.Conf.Root + if appPath != "" { + r, err := filepath.Abs(appPath) if err != nil { exception.New("Root error %s", 500, err.Error()).Throw() } + root = r + } + if envFile != "" { + config.Conf = config.LoadFrom(envFile) + } else { config.Conf = config.LoadFrom(filepath.Join(root, ".env")) - config.Conf.Root = root - if config.Conf.Mode == "production" { - config.Production() - } else if config.Conf.Mode == "development" { - config.Development() - } - - return } - if envFile != "" && appPath == "" { // 指定环境变量文件 - // config.SetEnvFile(envFile) - config.Conf = config.LoadFrom(envFile) - } - - if envFile != "" && appPath != "" { - root, err := filepath.Abs(appPath) - if err != nil { - exception.New("Root error %s", 500, err.Error()).Throw() - } - config.Conf = config.LoadFrom(envFile) - config.Conf.Root = root - if config.Conf.Mode == "production" { - config.Production() - } else if config.Conf.Mode == "development" { - config.Development() - } - + if config.Conf.Mode == "production" { + config.Production() + } else if config.Conf.Mode == "development" { + config.Development() } + fmt.Println(root) } From 148738ff232293cf870b2f7dd8873cc51af32e38 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 25 Feb 2022 21:23:48 +0800 Subject: [PATCH 03/96] debug: workflow --- .github/workflows/pr-receive.yml | 1 + .github/workflows/pr-test.yml | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-receive.yml b/.github/workflows/pr-receive.yml index c8be38ea..316b58a5 100644 --- a/.github/workflows/pr-receive.yml +++ b/.github/workflows/pr-receive.yml @@ -16,6 +16,7 @@ jobs: run: | mkdir -p ./pr echo ${{ github.event.number }} > ./pr/NR + echo ${{ github.event.pull_request.head.sha }} > ./pr/SHA - uses: actions/upload-artifact@v2 with: name: pr diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index e9eb0472..c89825f0 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -38,7 +38,14 @@ jobs: - name: "Unzip" run: | unzip pr.zip - ls -l + cat NR + cat SHA + echo HEAD=$(cat SHA) >> $GITHUB_ENV + + - name: Checkout pull request HEAD commit + uses: actions/checkout@v2 + with: + ref: ${{ env.HEAD }} - name: "Comment on PR" uses: actions/github-script@v3 From f4739b7861eab2e4d2f844cb1aa5ac27e24af3da Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 25 Feb 2022 21:36:44 +0800 Subject: [PATCH 04/96] [add] pr-test workflow --- .github/workflows/pr-test.yml | 127 +++++++++++++++++++++++++++++++- .github/workflows/unit-test.yml | 4 +- 2 files changed, 127 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index c89825f0..2f195b3a 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -7,14 +7,76 @@ on: workflows: ["Receive PR"] types: - completed +env: + YAO_DEV: ${{ github.WORKSPACE }} + YAO_ENV: development + YAO_ROOT: ${{ github.WORKSPACE }}/tests + YAO_HOST: 0.0.0.0 + YAO_PORT: 5099 + YAO_SESSION: "memory" + YAO_LOG: "./logs/application.log" + YAO_LOG_MODE: "TEXT" + YAO_JWT_SECRET: "bLp@bi!oqo-2U+hoTRUG" + YAO_DB_AESKEY: "ZLX=T&f6refeCh-ro*r@" + OSS_TEST_ID: ${{ secrets.OSS_TEST_ID}} + OSS_TEST_SECRET: ${{ secrets.OSS_TEST_SECRET}} + ROOT_PLUGIN: ${{ github.WORKSPACE }}/../../../data/gou-unit/plugins jobs: - upload: + UnitTest: runs-on: ubuntu-latest + strategy: + matrix: + go: [1.17] + db: [SQLite3] if: > ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} steps: + - name: Setup Cache + uses: actions/cache@v2 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Checkout Kun + uses: actions/checkout@v2 + with: + repository: yaoapp/kun + path: kun + + - name: Checkout Xun + uses: actions/checkout@v2 + with: + repository: yaoapp/xun + path: xun + + - name: Checkout Gou + uses: actions/checkout@v2 + with: + repository: yaoapp/gou + path: gou + + - name: Checkout V8Go + uses: actions/checkout@v2 + with: + repository: rogchap/v8go + ref: 5e91d3d9dcabd2986f901b6b31590e49fc3c4dd8 + path: v8go + + - name: Move Kun, Xun, Gou, V8Go + run: | + mv kun ../ + mv xun ../ + mv gou ../ + mv v8go ../ + ls -l . + ls -l ../ + - name: "Download artifact" uses: actions/github-script@v3.1.0 with: @@ -35,25 +97,86 @@ jobs: }); var fs = require('fs'); fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data)); + - name: "Unzip" run: | unzip pr.zip cat NR cat SHA echo HEAD=$(cat SHA) >> $GITHUB_ENV + echo NR=$(cat NR) >> $GITHUB_ENV - name: Checkout pull request HEAD commit uses: actions/checkout@v2 with: ref: ${{ env.HEAD }} + - name: Setup ${{ matrix.db }} + uses: ./.github/actions/setup-db + with: + kind: "${{ matrix.db }}" + db: "xiang" + user: "xiang" + password: ${{ secrets.UNIT_PASS }} + + - name: Setup Go ${{ matrix.go }} + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + + - name: Setup Go Tools + run: | + if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi + if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi + if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi + + - name: Setup ENV & Host + env: + PASSWORD: ${{ secrets.UNIT_PASS }} + run: | + sudo echo "127.0.0.1 local.iqka.com" | sudo tee -a /etc/hosts + echo "YAO_DB_DRIVER=$DB_DRIVER" >> $GITHUB_ENV + echo "GITHUB_WORKSPACE:\n" && ls -l $GITHUB_WORKSPACE + + if [ "$DB_DRIVER" = "mysql" ]; then + echo "YAO_DB_PRIMARY=$DB_USER:$PASSWORD@$DB_HOST" >> $GITHUB_ENV + elif [ "$DB_DRIVER" = "postgres" ]; then + echo "YAO_DB_PRIMARY=postgres://$DB_USER:$PASSWORD@$DB_HOST" >> $GITHUB_ENV + else + echo "YAO_DB_PRIMARY=$YAO_ROOT/$DB_HOST" >> $GITHUB_ENV + fi + + echo ".:\n" && ls -l . + echo "..:\n" && ls -l .. + ping -c 1 -t 1 local.iqka.com + + - name: Test Prepare + run: | + make vet + make fmt-check + make misspell-check + make plugin + mkdir -p $YAO_ROOT/plugins/ + mv $ROOT_PLUGIN/user.so $YAO_ROOT/plugins/ + ls -l $YAO_ROOT/plugins/ + make migrate + + - name: Run test + run: | + make test + + - name: Codecov Report + uses: codecov/codecov-action@v2 + with: + token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos + - name: "Comment on PR" uses: actions/github-script@v3 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | var fs = require('fs'); - var issue_number = Number(fs.readFileSync('./NR')); + var issue_number = ${NR}; await github.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index e7987d1c..6ef61264 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -80,7 +80,7 @@ jobs: kind: "${{ matrix.db }}" db: "xiang" user: "xiang" - password: 123456 + password: ${{ secrets.UNIT_PASS }} - name: Setup Go ${{ matrix.go }} uses: actions/setup-go@v2 @@ -95,7 +95,7 @@ jobs: - name: Setup ENV & Host env: - PASSWORD: 123456 + PASSWORD: ${{ secrets.UNIT_PASS }} run: | sudo echo "127.0.0.1 local.iqka.com" | sudo tee -a /etc/hosts echo "YAO_DB_DRIVER=$DB_DRIVER" >> $GITHUB_ENV From 094d70c537e81a770f696e6059128ee1d25d5d13 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 25 Feb 2022 21:51:59 +0800 Subject: [PATCH 05/96] fix PR test workflow --- .github/workflows/pr-test.yml | 78 +++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index 2f195b3a..738a86d8 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -33,6 +33,50 @@ jobs: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} steps: + - name: "Download artifact" + uses: actions/github-script@v3.1.0 + with: + script: | + var artifacts = await github.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{github.event.workflow_run.id }}, + }); + var matchArtifact = artifacts.data.artifacts.filter((artifact) => { + return artifact.name == "pr" + })[0]; + var download = await github.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + var fs = require('fs'); + fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data)); + + - name: "Read NR & SHA" + run: | + unzip pr.zip + cat NR + cat SHA + echo HEAD=$(cat SHA) >> $GITHUB_ENV + echo NR=$(cat NR) >> $GITHUB_ENV + + - name: "Comment on PR" + uses: actions/github-script@v3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { NR } = process.env + var fs = require('fs'); + var issue_number = NR; + await github.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue_number, + body: 'Thank you for the PR! The test workflow is running, the results of the run will be commented later.' + }); + - name: Setup Cache uses: actions/cache@v2 with: @@ -77,35 +121,6 @@ jobs: ls -l . ls -l ../ - - name: "Download artifact" - uses: actions/github-script@v3.1.0 - with: - script: | - var artifacts = await github.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: ${{github.event.workflow_run.id }}, - }); - var matchArtifact = artifacts.data.artifacts.filter((artifact) => { - return artifact.name == "pr" - })[0]; - var download = await github.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: matchArtifact.id, - archive_format: 'zip', - }); - var fs = require('fs'); - fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data)); - - - name: "Unzip" - run: | - unzip pr.zip - cat NR - cat SHA - echo HEAD=$(cat SHA) >> $GITHUB_ENV - echo NR=$(cat NR) >> $GITHUB_ENV - - name: Checkout pull request HEAD commit uses: actions/checkout@v2 with: @@ -175,11 +190,12 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | + const { NR } = process.env var fs = require('fs'); - var issue_number = ${NR}; + var issue_number = NR; await github.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue_number, - body: 'Everything is OK. Thank you for the PR!' + body: '✨DONE✨ Everything is OK. Thanks Again!' }); From 304bb1de0e22fd3f900a4bf2905eca7244a11295 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 25 Feb 2022 22:01:51 +0800 Subject: [PATCH 06/96] Add MySQL8.0 , MySQL5.7 to pr-test workflow --- .github/workflows/pr-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index 738a86d8..74e274ca 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -28,7 +28,7 @@ jobs: strategy: matrix: go: [1.17] - db: [SQLite3] + db: [MySQL8.0, MySQL5.7, SQLite3] if: > ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} From 06808b33b22deaf6c31f3f3ae5a563e6d15fc9d2 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 25 Feb 2022 22:16:17 +0800 Subject: [PATCH 07/96] v0.9.2 --- .github/workflows/pr-test.yml | 4 ++-- share/const.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index 74e274ca..759e9115 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -74,7 +74,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, issue_number: issue_number, - body: 'Thank you for the PR! The test workflow is running, the results of the run will be commented later.' + body: 'Thank you for the PR! The ${{ matrix.db }} test workflow is running, the results of the run will be commented later.' }); - name: Setup Cache @@ -197,5 +197,5 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, issue_number: issue_number, - body: '✨DONE✨ Everything is OK. Thanks Again!' + body: '✨DONE✨ ${{ matrix.db }} passed.' }); diff --git a/share/const.go b/share/const.go index fa412a88..3b0cc7fb 100644 --- a/share/const.go +++ b/share/const.go @@ -1,7 +1,7 @@ package share // VERSION 版本号 -const VERSION = "0.9.1" +const VERSION = "0.9.2" // BUILDIN 打包应用合成一个制品 const BUILDIN = false From 3c8385bb6ea7036f1cc28476f2d4aca986c2d1e7 Mon Sep 17 00:00:00 2001 From: k0a1a Date: Fri, 25 Feb 2022 22:44:36 +0800 Subject: [PATCH 08/96] fix: init config failed --- cmd/root.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 2bbc9d7b..8c82169e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -126,5 +126,4 @@ func Boot() { } else if config.Conf.Mode == "development" { config.Development() } - fmt.Println(root) } From 8566db96b09402facd7f7020a68f31c44a24ef76 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 1 Mar 2022 16:39:36 +0800 Subject: [PATCH 09/96] [add] Socket client mode support & fix watch bug --- cmd/start.go | 17 +++++--- data/bindata.go | 60 ++++++++++++++--------------- server/socket.go | 2 +- server/socket_test.go | 4 +- share/watch.go | 27 ++++--------- share/watch_test.go | 3 -- tests/servers/rfid.sock.json | 11 ++++-- tests/servers/rfid_client.sock.json | 12 ++++++ 8 files changed, 71 insertions(+), 65 deletions(-) create mode 100644 tests/servers/rfid_client.sock.json diff --git a/cmd/start.go b/cmd/start.go index a6667c66..278f597e 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -88,15 +88,20 @@ var startCmd = &cobra.Command{ service.Watch(config.Conf) } - // 启用内测功能 + // with the alpha features if startAlpha { - for _, srv := range gou.Servers { + for _, sock := range gou.Sockets { fmt.Println(color.WhiteString("\n---------------------------------")) - fmt.Println(color.WhiteString(srv.Name)) + fmt.Println(color.WhiteString(sock.Name)) fmt.Println(color.WhiteString("---------------------------------")) - fmt.Println(color.GreenString("Host: %s://%s", srv.Protocol, srv.Host)) - fmt.Println(color.GreenString("Port: %s\n\n", srv.Port)) - go srv.Start() + fmt.Println(color.GreenString("Mode: %s", sock.Mode)) + fmt.Println(color.GreenString("Host: %s://%s", sock.Protocol, sock.Host)) + fmt.Println(color.GreenString("Port: %s\n\n", sock.Port)) + if sock.Mode == "server" { + go sock.Start() + } else if sock.Mode == "client" { + go sock.Connect() + } } } diff --git a/data/bindata.go b/data/bindata.go index e6284483..307356b6 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -208,7 +208,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 22, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 22, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -228,7 +228,7 @@ func yaoDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -248,7 +248,7 @@ func yaoApisReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -268,7 +268,7 @@ func yaoApisChartHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/chart.http.json", size: 624, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/chart.http.json", size: 624, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -288,7 +288,7 @@ func yaoApisImportHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/import.http.json", size: 1676, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/import.http.json", size: 1676, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -308,7 +308,7 @@ func yaoApisPageHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/page.http.json", size: 609, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/page.http.json", size: 609, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -328,7 +328,7 @@ func yaoApisStorageHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/storage.http.json", size: 1092, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/storage.http.json", size: 1092, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -348,7 +348,7 @@ func yaoApisTableHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/table.http.json", size: 3196, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/table.http.json", size: 3196, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -368,7 +368,7 @@ func yaoApisUserHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/user.http.json", size: 1453, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/user.http.json", size: 1453, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -388,7 +388,7 @@ func yaoApisXiangHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/xiang.http.json", size: 1239, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/apis/xiang.http.json", size: 1239, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -408,7 +408,7 @@ func yaoDataDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -428,7 +428,7 @@ func yaoDataAppJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/app.json", size: 277, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/app.json", size: 277, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -448,7 +448,7 @@ func yaoDataIconsDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/icons/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -468,7 +468,7 @@ func yaoDataIcons404Png() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -488,7 +488,7 @@ func yaoDataIconsIconIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -508,7 +508,7 @@ func yaoDataIconsIconIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -528,7 +528,7 @@ func yaoDataIconsIconPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -548,7 +548,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -568,7 +568,7 @@ func yaoFlowsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -588,7 +588,7 @@ func yaoFlowsAdminGuardFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/admin/guard.flow.json", size: 559, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/flows/admin/guard.flow.json", size: 559, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -608,7 +608,7 @@ func yaoFlowsMenuFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -628,7 +628,7 @@ func yaoFlowsUserFindFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/user/find.flow.json", size: 328, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/flows/user/find.flow.json", size: 328, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -648,7 +648,7 @@ func yaoLangsEnUsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -668,7 +668,7 @@ func yaoModelsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -688,7 +688,7 @@ func yaoModelsMenuModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/menu.mod.json", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/models/menu.mod.json", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -708,7 +708,7 @@ func yaoModelsUserModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user.mod.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -728,7 +728,7 @@ func yaoModelsWorkflowModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/workflow.mod.json", size: 3333, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/models/workflow.mod.json", size: 3333, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -748,7 +748,7 @@ func yaoTablesReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -768,7 +768,7 @@ func yaoTablesMenuTabJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/menu.tab.json", size: 10471, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/tables/menu.tab.json", size: 10471, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -788,7 +788,7 @@ func yaoTablesUserTabJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/user.tab.json", size: 2357, mode: os.FileMode(420), modTime: time.Unix(1645092418, 0)} + info := bindataFileInfo{name: "yao/tables/user.tab.json", size: 2357, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/server/socket.go b/server/socket.go index 0ab88c5e..48b07ea0 100644 --- a/server/socket.go +++ b/server/socket.go @@ -26,7 +26,7 @@ func LoadFrom(dir string, prefix string) error { err := share.Walk(dir, ".sock.json", func(root, filename string) { name := prefix + share.SpecName(root, filename) content := share.ReadFile(filename) - _, err := gou.LoadServer(string(content), name) + _, err := gou.LoadSocket(string(content), name) if err != nil { log.With(log.F{"root": root, "file": filename}).Error(err.Error()) } diff --git a/server/socket_test.go b/server/socket_test.go index 65f8d361..f86a5a47 100644 --- a/server/socket_test.go +++ b/server/socket_test.go @@ -16,8 +16,8 @@ func TestLoad(t *testing.T) { func check(t *testing.T) { keys := []string{} - for key := range gou.Servers { + for key := range gou.Sockets { keys = append(keys, key) } - assert.Equal(t, 1, len(keys)) + assert.Equal(t, 2, len(keys)) } diff --git a/share/watch.go b/share/watch.go index 41a13ac2..9d25802f 100644 --- a/share/watch.go +++ b/share/watch.go @@ -6,13 +6,14 @@ import ( "log" "os" "path/filepath" + "sync" "github.com/fatih/color" "github.com/fsnotify/fsnotify" "github.com/yaoapp/kun/exception" ) -var watchDone = []chan bool{} +var watchGroup sync.WaitGroup var watchOp = map[fsnotify.Op]string{ fsnotify.Create: "create", fsnotify.Write: "write", @@ -27,10 +28,12 @@ func Watch(root string, cb func(op string, file string)) { if err != nil { log.Fatal(err) } - defer watcher.Close() + defer func() { + watcher.Close() + watchGroup.Done() + }() - watchDone = append(watchDone, make(chan bool)) - last := len(watchDone) - 1 + watchGroup.Add(1) go func() { for { select { @@ -86,20 +89,6 @@ func Watch(root string, cb func(op string, file string)) { return nil }) - select { - case v := <-watchDone[last]: - fmt.Println(color.GreenString("Stop Watching: %s", root)) - if v == true { - break - } - } -} + watchGroup.Wait() -// StopWatch 停止监听 -func StopWatch() { - for i := range watchDone { - fmt.Println(color.GreenString("Stop: %d", i)) - watchDone[i] <- true - } - watchDone = []chan bool{} } diff --git a/share/watch_test.go b/share/watch_test.go index bc6e49e6..e05fff00 100644 --- a/share/watch_test.go +++ b/share/watch_test.go @@ -5,7 +5,6 @@ import ( "os" "path" "testing" - "time" "github.com/stretchr/testify/assert" ) @@ -16,7 +15,5 @@ func TestWatch(t *testing.T) { go Watch(root, func(op string, file string) { log.Println(op, file) }) - time.Sleep(time.Second * 2) - defer StopWatch() }) } diff --git a/tests/servers/rfid.sock.json b/tests/servers/rfid.sock.json index 7f908a77..e2716f16 100644 --- a/tests/servers/rfid.sock.json +++ b/tests/servers/rfid.sock.json @@ -1,9 +1,12 @@ { - "name": "RFID接收器", - "description": "用来接收并处理RIFD", - "version": "0.0.1", + "name": "RFID Receiver (Server Mode)", + "description": "RFID Receiver (Server Mode)", + "mode": "server", + "version": "0.9.2", "protocol": "tcp", "host": "0.0.0.0", "port": "3019", - "process": "flows.rfid.read" + "process": "flows.rfid.read", + "buffer": 60, + "keep": 0 } diff --git a/tests/servers/rfid_client.sock.json b/tests/servers/rfid_client.sock.json new file mode 100644 index 00000000..7889de6e --- /dev/null +++ b/tests/servers/rfid_client.sock.json @@ -0,0 +1,12 @@ +{ + "name": "RFID Receiver (Client Mode)", + "description": "RFID Receiver (Client Mode)", + "mode": "client", + "version": "0.9.2", + "protocol": "tcp", + "host": "192.168.1.192", + "port": "6000", + "process": "flows.rfid.read", + "buffer": 60, + "keep": 0 +} From 80965c78e3f223ca4e0f6090d61a86184bbb2ef7 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 1 Mar 2022 20:47:14 +0800 Subject: [PATCH 10/96] [add] xiang.helper.HexToString process --- flow/flow_test.go | 2 +- helper/hex.process.go | 22 +++++++++ helper/hex_test.go | 61 ++++++++++++++++++++++++ helper/process.go | 3 ++ tests/flows/helper/hextostring.flow.json | 13 +++++ tests/scripts/helper.js | 18 +++++++ 6 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 helper/hex.process.go create mode 100644 helper/hex_test.go create mode 100644 tests/flows/helper/hextostring.flow.json create mode 100644 tests/scripts/helper.js diff --git a/flow/flow_test.go b/flow/flow_test.go index 429cb9f3..79f13836 100644 --- a/flow/flow_test.go +++ b/flow/flow_test.go @@ -20,5 +20,5 @@ func check(t *testing.T) { for key := range gou.Flows { keys = append(keys, key) } - assert.Equal(t, 22, len(keys)) + assert.Equal(t, 23, len(keys)) } diff --git a/helper/hex.process.go b/helper/hex.process.go new file mode 100644 index 00000000..d43be53a --- /dev/null +++ b/helper/hex.process.go @@ -0,0 +1,22 @@ +package helper + +import ( + "encoding/hex" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" +) + +// ProcessHexToString xiang.helper.HexToString +func ProcessHexToString(process *gou.Process) interface{} { + process.ValidateArgNums(1) + + switch process.Args[0].(type) { + case string: + return hex.EncodeToString([]byte(process.Args[0].(string))) + case []byte: + return hex.EncodeToString(process.Args[0].([]byte)) + } + log.With(log.F{"input": process.Args[0]}).Error("HexToString: type does not support") + return nil +} diff --git a/helper/hex_test.go b/helper/hex_test.go new file mode 100644 index 00000000..bca5e63a --- /dev/null +++ b/helper/hex_test.go @@ -0,0 +1,61 @@ +package helper + +import ( + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" + "github.com/yaoapp/yao/flow" + "github.com/yaoapp/yao/share" +) + +func TestProcessHexToString(t *testing.T) { + res, err := gou.NewProcess("xiang.helper.HexToString", []byte{0x0, 0x1}).Exec() + assert.Nil(t, err) + assert.Equal(t, "0001", res) + + res, err = gou.NewProcess("xiang.helper.HexToString", string([]byte{0x0, 0x1})).Exec() + assert.Nil(t, err) + assert.Equal(t, "0001", res) + + res, err = gou.NewProcess("xiang.helper.HexToString", 1024).Exec() + assert.Nil(t, err) + assert.Nil(t, res) +} + +func TestProcessHexToStringInFlow(t *testing.T) { + testflow := path.Join(os.Getenv("YAO_DEV"), "tests", "flows", "helper") + flow.LoadFrom(testflow, "helper.") + + res, err := gou.NewProcess("flows.helper.HexToString").Exec() + assert.Nil(t, err) + assert.Equal(t, "6162", res) // ab +} + +func TestProcessHexToStringInScript(t *testing.T) { + testscirpt := path.Join(os.Getenv("YAO_DEV"), "tests", "scripts") + share.LoadFrom(testscirpt) + + res, err := gou.NewProcess("scripts.helper.HexToStringString").Exec() + assert.Nil(t, err) + assert.Equal(t, "6162", res) // ab + + res, err = gou.NewProcess("scripts.helper.HexToStringBytes", []byte{0x0, 0x1}).Exec() + assert.Nil(t, err) + assert.Equal(t, "0001", res) // []byte{0x0, 0x1} +} + +func TestProcessBufferInScript(t *testing.T) { + testscirpt := path.Join(os.Getenv("YAO_DEV"), "tests", "scripts") + share.LoadFrom(testscirpt) + + res, err := gou.NewProcess("scripts.helper.Buffer").Exec() + assert.Nil(t, err) + + str, ok := res.(string) + assert.True(t, ok) + assert.Equal(t, []byte{0x0, 0x1}, []byte(str)) + +} diff --git a/helper/process.go b/helper/process.go index 73d43e24..7de681f8 100644 --- a/helper/process.go +++ b/helper/process.go @@ -27,6 +27,8 @@ func init() { gou.RegisterProcessHandler("xiang.helper.MapDel", ProcessMapDel) gou.RegisterProcessHandler("xiang.helper.MapMultiDel", ProcessMapMultiDel) + gou.RegisterProcessHandler("xiang.helper.HexToString", ProcessHexToString) + gou.RegisterProcessHandler("xiang.helper.StrConcat", ProcessStrConcat) gou.RegisterProcessHandler("xiang.helper.Captcha", ProcessCaptcha) @@ -64,6 +66,7 @@ func init() { gou.RegisterProcessHandler("xiang.flow.Sleep", ProcessSleep) gou.AliasProcess("xiang.flow.Sleep", "xiang.sys.Sleep") + } // ProcessPrint xiang.helper.Print 打印语句 diff --git a/tests/flows/helper/hextostring.flow.json b/tests/flows/helper/hextostring.flow.json new file mode 100644 index 00000000..48fc2375 --- /dev/null +++ b/tests/flows/helper/hextostring.flow.json @@ -0,0 +1,13 @@ +{ + "label": "HexToString", + "version": "1.0.0", + "description": "HexToString", + "nodes": [ + { + "name": "Value", + "process": "xiang.helper.HexToString", + "args": ["ab"] + } + ], + "output": "{{$res.Value}}" +} diff --git a/tests/scripts/helper.js b/tests/scripts/helper.js new file mode 100644 index 00000000..05b95d1f --- /dev/null +++ b/tests/scripts/helper.js @@ -0,0 +1,18 @@ +function HexToStringString() { + return Process("xiang.helper.HexToString", "ab"); +} + +function HexToStringBytes(data) { + return Process("xiang.helper.HexToString", data); +} + +function Buffer() { + var buffer = new Uint8Array(2); + buffer[0] = 0x0; + buffer[1] = 0x1; + return BufferToString(buffer); +} + +function BufferToString(buffer) { + return String.fromCharCode.apply(null, new Uint8Array(buffer)); +} From deb6ba9135e89cba56c531a2ca8b39fa7ac2058e Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 4 Mar 2022 21:43:19 +0800 Subject: [PATCH 11/96] [add] WebSocket support --- api/api.go | 11 +++++++++++ api/api_test.go | 8 ++++++++ cmd/start.go | 8 ++++++++ flow/flow_test.go | 2 +- go.sum | 2 ++ service/middleware.go | 3 ++- tests/apis/chat.ws.json | 11 +++++++++++ tests/flows/websocket/chat.flow.json | 7 +++++++ tests/scripts/websocket.js | 5 +++++ 9 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 tests/apis/chat.ws.json create mode 100644 tests/flows/websocket/chat.flow.json create mode 100644 tests/scripts/websocket.js diff --git a/api/api.go b/api/api.go index 5af1c70c..7da8627c 100644 --- a/api/api.go +++ b/api/api.go @@ -31,6 +31,17 @@ func LoadFrom(dir string, prefix string) error { log.With(log.F{"root": root, "file": filename}).Error(err.Error()) } }) + + // Load WebSocket Server + err = share.Walk(dir, ".ws.json", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + _, err := gou.LoadWebSocket(string(content), name) + if err != nil { + log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + } + }) + return err } diff --git a/api/api_test.go b/api/api_test.go index 1fcb35b3..79f8cf18 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/websocket" "github.com/yaoapp/yao/config" ) @@ -20,5 +21,12 @@ func check(t *testing.T) { for key := range gou.APIs { keys = append(keys, key) } + + wskeys := []string{} + for key := range websocket.Upgraders { + wskeys = append(wskeys, key) + } + assert.Equal(t, 4, len(keys)) + assert.Equal(t, 1, len(wskeys)) } diff --git a/cmd/start.go b/cmd/start.go index 278f597e..9395e44c 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -9,6 +9,7 @@ import ( "github.com/fatih/color" "github.com/spf13/cobra" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/websocket" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/engine" "github.com/yaoapp/yao/service" @@ -63,7 +64,14 @@ var startCmd = &cobra.Command{ color.WhiteString(filepath.Join("/api", api.HTTP.Group, p.Path)), "\tprocess:", p.Process) } + } + fmt.Printf(color.CyanString("\n%s(%d)\n", "WebSocket", len(websocket.Upgraders))) + for name, upgrader := range websocket.Upgraders { // WebSocket + fmt.Println( + colorMehtod("GET"), + color.WhiteString(filepath.Join("/websocket", name)), + "\tprocess:", upgrader.Process) } } diff --git a/flow/flow_test.go b/flow/flow_test.go index 79f13836..394da9b4 100644 --- a/flow/flow_test.go +++ b/flow/flow_test.go @@ -20,5 +20,5 @@ func check(t *testing.T) { for key := range gou.Flows { keys = append(keys, key) } - assert.Equal(t, 23, len(keys)) + assert.Equal(t, 24, len(keys)) } diff --git a/go.sum b/go.sum index 971128fe..fe8aee73 100644 --- a/go.sum +++ b/go.sum @@ -223,6 +223,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= diff --git a/service/middleware.go b/service/middleware.go index ed2e746b..fe98f2f6 100644 --- a/service/middleware.go +++ b/service/middleware.go @@ -27,7 +27,8 @@ func BinStatic(c *gin.Context) { length := len(c.Request.URL.Path) - if length >= 5 && c.Request.URL.Path[0:5] == "/api/" { // API接口 + if (length >= 5 && c.Request.URL.Path[0:5] == "/api/") || + (length >= 11 && c.Request.URL.Path[0:11] == "/websocket/") { // API & websocket c.Next() return } else if length >= 7 && c.Request.URL.Path[0:7] == "/xiang/" { // 数据管理后台 diff --git a/tests/apis/chat.ws.json b/tests/apis/chat.ws.json new file mode 100644 index 00000000..eb94daed --- /dev/null +++ b/tests/apis/chat.ws.json @@ -0,0 +1,11 @@ +{ + "name": "A Chat WebSocket server", + "description": "A Chat WebSocket serverr", + "version": "0.9.2", + "protocols": ["yao-chat-01"], + "guard": "bearer-jwt", + "buffer": { "read": 1024, "write": 1024 }, + "limit": { "read-wait": 10, "pong-wait": 20, "max-message": 1024 }, + "timeout": 5, + "process": "flows.websocket.chat" +} diff --git a/tests/flows/websocket/chat.flow.json b/tests/flows/websocket/chat.flow.json new file mode 100644 index 00000000..e1dedde2 --- /dev/null +++ b/tests/flows/websocket/chat.flow.json @@ -0,0 +1,7 @@ +{ + "label": "a chat Websocket process", + "version": "1.0.0", + "description": "a chat Websocket process", + "nodes": [], + "output": "{{$in.0}}" +} diff --git a/tests/scripts/websocket.js b/tests/scripts/websocket.js new file mode 100644 index 00000000..557929e3 --- /dev/null +++ b/tests/scripts/websocket.js @@ -0,0 +1,5 @@ +function Hello() { + var ws = new WebSocket("ws://127.0.0.1:5093/websocket/chat", "p0"); + var response = ws.push("Hello World"); + return response; +} From 5332efc532a5d45e7c0ee15b878ae9c5c410c6ac Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 5 Mar 2022 20:06:04 +0800 Subject: [PATCH 12/96] [add] yao service command --- cmd/root.go | 1 + cmd/service.go | 173 ++++++++++++++++++++++++++++++++ go.sum | 8 +- tests/services/cmd.srv.json | 15 +++ tests/services/process.srv.json | 15 +++ 5 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 cmd/service.go create mode 100644 tests/services/cmd.srv.json create mode 100644 tests/services/process.srv.json diff --git a/cmd/root.go b/cmd/root.go index 8c82169e..1106e555 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -91,6 +91,7 @@ func init() { startCmd, runCmd, initCmd, + serviceCmd, ) // rootCmd.SetHelpCommand(helpCmd) rootCmd.PersistentFlags().StringVarP(&appPath, "app", "a", "", L("Application directory")) diff --git a/cmd/service.go b/cmd/service.go new file mode 100644 index 00000000..59e516fd --- /dev/null +++ b/cmd/service.go @@ -0,0 +1,173 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +var serviceCmd = &cobra.Command{ + Use: "service", + Short: L("Service manager"), + Long: L("Service manager"), + Run: func(cmd *cobra.Command, args []string) { + loadService(config.Conf) + command := "ps" + if len(args) > 0 { + command = args[0] + } + + if len(args) > 1 { + args = args[1:] + } else { + args = []string{} + } + + switch command { + case "ps": + services() + break + case "install", "start", "stop", "remove", "status": + serviceManage(command, args) + break + default: + serviceHelp() + } + }, +} + +func serviceHelp() { + fmt.Println("Usage:") + fmt.Println(" ", color.GreenString(L("%s service [ps|start|stop|install|status] [all|name]"), share.BUILDNAME)) + fmt.Println("") +} + +func serviceManageAll(command string) { + for name := range gou.Services { + if name == "all" { + continue + } + serviceManage(command, []string{name}) + } +} + +func serviceManage(command string, args []string) { + if len(args) == 0 { + serviceHelp() + os.Exit(1) + } + + defer func() { + err := exception.Catch(recover()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + }() + + name := args[0] + if name == "all" { + serviceManageAll(command) + return + } + + srv := gou.SelectService(name) + var status = "" + var err error + switch command { + case "install": + status, err = srv.Install() + break + case "start": + status, err = srv.Start() + break + case "stop": + status, err = srv.Stop() + break + case "remove": + status, err = srv.Remove() + break + case "status": + status, err = srv.Status() + break + } + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + + fmt.Println(status) +} + +func services() { + for name, srv := range gou.Services { + status, err := srv.Status() + if err != nil { + status = err.Error() + } + fmt.Println( + color.GreenString("%s", name), + srv.Name, "....", + statusInfo(status), + ) + } + fmt.Println("") +} + +func statusInfo(message string) string { + if strings.Contains(message, "stopped") { + return statusStop() + } else if strings.Contains(message, "not installed") { + return statusNotInstalled() + } else if strings.Contains(message, "running") { + return statusRunning() + } + return statusUnknown() +} + +func statusUnknown() string { + return fmt.Sprintf("[ %s ]", color.WhiteString("UNKNOWN")) +} + +func statusRunning() string { + return fmt.Sprintf("[ %s ]", color.GreenString("RUNNING")) +} + +func statusNotInstalled() string { + return fmt.Sprintf("[ %s ]", color.YellowString("NOT INSTALLED")) +} + +func statusStop() string { + return fmt.Sprintf("[ %s ]", color.RedString("STOPED")) +} + +// Load 加载API +func loadService(cfg config.Config) { + var root = filepath.Join(cfg.Root, "services") + loadServiceFrom(root, "") +} + +func loadServiceFrom(dir string, prefix string) error { + + if share.DirNotExists(dir) { + return fmt.Errorf("%s does not exists", dir) + } + + err := share.Walk(dir, ".srv.json", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + _, err := gou.LoadService(string(content), name) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + }) + + return err +} diff --git a/go.sum b/go.sum index fe8aee73..0df3fe11 100644 --- a/go.sum +++ b/go.sum @@ -403,6 +403,7 @@ github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9 github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f h1:a7clxaGmmqtdNTXyvrp/lVO/Gnkzlhc/+dLs5v965GM= github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f/go.mod h1:/mK7FZ3mFYEn9zvNPhpngTyatyehSwte5bJZ4ehL5Xw= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -445,6 +446,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/takama/daemon v1.0.0 h1:XS3VLnFKmqw2Z7fQ/dHRarrVjdir9G3z7BEP8osjizQ= +github.com/takama/daemon v1.0.0/go.mod h1:gKlhcjbqtBODg5v9H1nj5dU1a2j2GemtuWSNLD5rxOE= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= @@ -638,6 +641,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -661,8 +665,8 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220207234003-57398862261d h1:Bm7BNOQt2Qv7ZqysjeLjgCBanX+88Z/OtdvsrEv1Djc= -golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/tests/services/cmd.srv.json b/tests/services/cmd.srv.json new file mode 100644 index 00000000..844a2386 --- /dev/null +++ b/tests/services/cmd.srv.json @@ -0,0 +1,15 @@ +{ + "name": "Service for receiving RFID", + "description": "Service for receiving RFID", + "version": "0.9.2", + "restart": "on-failure", + "requires": ["syslog"], + "after": ["syslog"], + "error": "/var/log/test.err", + "output": "/var/log/test.log", + "workdir": "~", + "command": "tail", + "args": ["-f", "/dev/null"], + "user": "root", + "group": "root" +} diff --git a/tests/services/process.srv.json b/tests/services/process.srv.json new file mode 100644 index 00000000..5c32b997 --- /dev/null +++ b/tests/services/process.srv.json @@ -0,0 +1,15 @@ +{ + "name": "Service for receiving RFID", + "description": "Service for receiving RFID", + "version": "0.9.2", + "restart": "on-failure", + "requires": ["syslog"], + "after": ["syslog"], + "error": "/var/log/test.err", + "output": "/var/log/test.log", + "workdir": "~", + "process": "xiang.sys.ping", + "args": [], + "user": "root", + "group": "root" +} From a99d75a91bbc1afaacb6f65cabfcc0286100ad88 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 5 Mar 2022 21:28:19 +0800 Subject: [PATCH 13/96] [add] process xiang.server.* --- server/process.go | 57 +++++++++++++++++++++++++++++++++++++++++++ server/socket_test.go | 14 +++++++++++ 2 files changed, 71 insertions(+) create mode 100644 server/process.go diff --git a/server/process.go b/server/process.go new file mode 100644 index 00000000..8cd7ce37 --- /dev/null +++ b/server/process.go @@ -0,0 +1,57 @@ +package server + +import ( + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" +) + +func init() { + gou.RegisterProcessHandler("xiang.server.Start", ProcessStart) + gou.RegisterProcessHandler("xiang.server.Connect", ProcessConnect) +} + +// ProcessStart xiang.server.Start +func ProcessStart(process *gou.Process) interface{} { + process.ValidateArgNums(1) + + name := process.ArgsString(0) + serv, has := gou.Sockets[name] + if !has { + exception.New("%s does not load", 400, name).Throw() + } + + args := []interface{}{} + if process.NumOfArgs() > 1 { + args = process.Args[1:] + } + + if serv.Mode != "server" { + exception.New("%s mode [%s] not server", 400, name, serv.Mode).Throw() + } + + serv.Start(args...) + return nil +} + +// ProcessConnect xiang.server.Connect +func ProcessConnect(process *gou.Process) interface{} { + process.ValidateArgNums(1) + + name := process.ArgsString(0) + serv, has := gou.Sockets[name] + if !has { + exception.New("%s does not load", 400, name).Throw() + } + + args := []interface{}{} + if process.NumOfArgs() > 1 { + args = process.Args[1:] + } + + if serv.Mode != "client" { + exception.New("%s mode [%s] not server", 400, name, serv.Mode).Throw() + } + + serv.Connect(args...) + return nil +} diff --git a/server/socket_test.go b/server/socket_test.go index f86a5a47..05af1a9d 100644 --- a/server/socket_test.go +++ b/server/socket_test.go @@ -14,6 +14,20 @@ func TestLoad(t *testing.T) { check(t) } +func TestProcessStart(t *testing.T) { + Load(config.Conf) + // assert.NotPanics(t, func() { + // gou.NewProcess("xiang.server.Start", "rfid").Run() + // }) +} + +func TestProcessConnect(t *testing.T) { + Load(config.Conf) + // assert.NotPanics(t, func() { + // gou.NewProcess("xiang.server.Connect", "rfid_client").Run() + // }) +} + func check(t *testing.T) { keys := []string{} for key := range gou.Sockets { From fe965c8a0274e7610e3e26b092dfe9fa77ce8e31 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 8 Mar 2022 20:23:56 +0800 Subject: [PATCH 14/96] [fix] Table process session bug --- table/process.go | 56 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/table/process.go b/table/process.go index 82a071be..10bc0e20 100644 --- a/table/process.go +++ b/table/process.go @@ -52,7 +52,10 @@ func ProcessSearch(process *gou.Process) interface{} { pagesize := process.ArgsInt(3, api.DefaultInt(2)) // 查询数据 - response := gou.NewProcess(api.Process, param, page, pagesize).Run() + response := gou.NewProcess(api.Process, param, page, pagesize). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() // After Hook return table.After(table.Hooks.AfterSearch, response, []interface{}{param, page, pagesize}, process.Sid) @@ -77,7 +80,10 @@ func ProcessFind(process *gou.Process) interface{} { param := api.MergeDefaultQueryParam(gou.QueryParam{}, 1, process.Sid) // 查询数据 - response := gou.NewProcess(api.Process, id, param).Run() + response := gou.NewProcess(api.Process, id, param). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() // After Hook return table.After(table.Hooks.AfterFind, response, []interface{}{id, param}, process.Sid) @@ -101,7 +107,10 @@ func ProcessSave(process *gou.Process) interface{} { process.ValidateArgNums(2) // 查询数据 - response := gou.NewProcess(api.Process, process.Args[1]).Run() + response := gou.NewProcess(api.Process, process.Args[1]). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() // After Hook return table.After(table.Hooks.AfterSave, response, []interface{}{process.Args[1]}, process.Sid) @@ -117,7 +126,10 @@ func ProcessDelete(process *gou.Process) interface{} { table.APIGuard(api.Guard, process.Sid, process.Global) id := process.Args[1] - return gou.NewProcess(api.Process, id).Run() + return gou.NewProcess(api.Process, id). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() } // ProcessDeleteWhere xiang.table.DeleteWhere @@ -136,7 +148,10 @@ func ProcessDeleteWhere(process *gou.Process) interface{} { param.Limit = 10 } - return gou.NewProcess(api.Process, param).Run() + return gou.NewProcess(api.Process, param). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() } // ProcessDeleteIn xiang.table.DeleteIn @@ -158,7 +173,10 @@ func ProcessDeleteIn(process *gou.Process) interface{} { }, } - return gou.NewProcess(api.Process, param).Run() + return gou.NewProcess(api.Process, param). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() } // ProcessUpdateWhere xiang.table.UpdateWhere @@ -176,7 +194,10 @@ func ProcessUpdateWhere(process *gou.Process) interface{} { if param.Limit == 0 { // 限定删除行 param.Limit = 10 } - return gou.NewProcess(api.Process, param, process.Args[2]).Run() + return gou.NewProcess(api.Process, param, process.Args[2]). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() } // ProcessUpdateIn xiang.table.UpdateWhere @@ -197,7 +218,10 @@ func ProcessUpdateIn(process *gou.Process) interface{} { {Column: primary, OP: "in", Value: ids}, }, } - return gou.NewProcess(api.Process, param, process.Args[3]).Run() + return gou.NewProcess(api.Process, param, process.Args[3]). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() } // ProcessInsert xiang.table.Insert @@ -208,7 +232,9 @@ func ProcessInsert(process *gou.Process) interface{} { table := Select(name) api := table.APIs["insert"].ValidateLoop("xiang.table.Insert") table.APIGuard(api.Guard, process.Sid, process.Global) - return gou.NewProcess(api.Process, process.Args[1:]...).Run() + return gou.NewProcess(api.Process, process.Args[1:]...). + WithGlobal(process.Global). + WithSID(process.Sid).Run() } // ProcessSetting xiang.table.Setting @@ -254,7 +280,9 @@ func ProcessSetting(process *gou.Process) interface{} { return setting } - return gou.NewProcess(api.Process, fields).Run() + return gou.NewProcess(api.Process, fields). + WithGlobal(process.Global). + WithSID(process.Sid).Run() } // ProcessQuickSave xiang.table.QuickSave @@ -284,7 +312,9 @@ func ProcessQuickSave(process *gou.Process) interface{} { args = append(args, payload.Get("query")) } - return gou.NewProcess(api.Process, args...).Run() + return gou.NewProcess(api.Process, args...). + WithGlobal(process.Global). + WithSID(process.Sid).Run() } // ProcessSelect xiang.table.Select @@ -299,7 +329,9 @@ func ProcessSelect(process *gou.Process) interface{} { // Before Hook process.Args = table.Before(table.Hooks.BeforeSelect, process.Args, process.Sid) - response := gou.NewProcess(api.Process, process.Args[1:]...).Run() + response := gou.NewProcess(api.Process, process.Args[1:]...). + WithGlobal(process.Global). + WithSID(process.Sid).Run() // After Hook return table.After(table.Hooks.AfterSelect, response, process.Args[1:], process.Sid) From 79dd564803bcb3b66efebaf1e4b9b8e80584cd59 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 18 Mar 2022 20:18:58 +0800 Subject: [PATCH 15/96] [add] api :headers & kv process (30%) --- go.mod | 1 - go.sum | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c41c60d8..6405a23d 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,6 @@ require ( github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v1.1.5 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/memberlist v0.3.0 // indirect github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect github.com/jmoiron/sqlx v1.3.4 // indirect diff --git a/go.sum b/go.sum index 0df3fe11..90abbb39 100644 --- a/go.sum +++ b/go.sum @@ -80,6 +80,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -101,6 +102,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -116,6 +118,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -144,10 +147,14 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -202,6 +209,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -278,6 +286,7 @@ github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hokaccha/go-prettyjson v0.0.0-20210113012101-fb4e108d2519 h1:nqAlWFEdqI0ClbTDrhDvE/8LeQ4pftrqKUX9w5k0j3s= github.com/hokaccha/go-prettyjson v0.0.0-20210113012101-fb4e108d2519/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -369,9 +378,17 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwd github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0= github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -536,6 +553,7 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -561,6 +579,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -574,6 +593,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -608,6 +628,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -620,12 +641,14 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -648,6 +671,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -733,6 +757,7 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -864,11 +889,13 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 44c041decfc5ad9ad44779bd0230f6632b62281d Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 27 Mar 2022 20:47:57 +0800 Subject: [PATCH 16/96] [add] Store Process & Javascript Object --- engine/load.go | 8 ++++++++ store/store.go | 36 ++++++++++++++++++++++++++++++++++++ store/store_test.go | 23 +++++++++++++++++++++++ tests/stores/share.lru.json | 6 ++++++ 4 files changed, 73 insertions(+) create mode 100644 store/store.go create mode 100644 store/store_test.go create mode 100644 tests/stores/share.lru.json diff --git a/engine/load.go b/engine/load.go index 4046165a..bef0cdd1 100644 --- a/engine/load.go +++ b/engine/load.go @@ -21,6 +21,7 @@ import ( "github.com/yaoapp/yao/script" "github.com/yaoapp/yao/server" "github.com/yaoapp/yao/share" + "github.com/yaoapp/yao/store" "github.com/yaoapp/yao/table" "github.com/yaoapp/yao/workflow" ) @@ -56,6 +57,7 @@ func Load(cfg config.Config) (err error) { if err != nil { log.Warn(err.Error()) } + err = script.Load(cfg) // 加载JS处理器 script if err != nil { log.Warn(err.Error()) @@ -71,6 +73,12 @@ func Load(cfg config.Config) (err error) { if err != nil { log.Warn(err.Error()) } + + err = store.Load(cfg) // Load stores + if err != nil { + log.Warn(err.Error()) + } + err = plugin.Load(cfg) // 加载业务插件 plugin if err != nil { log.Warn(err.Error()) diff --git a/store/store.go b/store/store.go new file mode 100644 index 00000000..4b5e3db6 --- /dev/null +++ b/store/store.go @@ -0,0 +1,36 @@ +package store + +import ( + "fmt" + "path/filepath" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +// Load load store +func Load(cfg config.Config) error { + var root = filepath.Join(cfg.Root, "stores") + return LoadFrom(root, "") +} + +// LoadFrom load from dir +func LoadFrom(dir string, prefix string) error { + + if share.DirNotExists(dir) { + return fmt.Errorf("%s does not exists", dir) + } + + err := share.Walk(dir, ".json", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + _, err := gou.LoadStore(string(content), name) + if err != nil { + log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + } + }) + + return err +} diff --git a/store/store_test.go b/store/store_test.go new file mode 100644 index 00000000..c569579f --- /dev/null +++ b/store/store_test.go @@ -0,0 +1,23 @@ +package store + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou/kv" + "github.com/yaoapp/yao/config" +) + +func TestLoad(t *testing.T) { + Load(config.Conf) + LoadFrom("not a path", "404.") + check(t) +} + +func check(t *testing.T) { + keys := []string{} + for key := range kv.Pools { + keys = append(keys, key) + } + assert.Equal(t, 1, len(keys)) +} diff --git a/tests/stores/share.lru.json b/tests/stores/share.lru.json new file mode 100644 index 00000000..7ecbd3a7 --- /dev/null +++ b/tests/stores/share.lru.json @@ -0,0 +1,6 @@ +{ + "name": "Share LRU Cache", + "description": "The share memory LRU cache", + "type": "lru", + "option": { "size": 1024 } +} From 602f8e5c759cac426dcde7dfcceeade4c28615b2 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 30 Mar 2022 23:11:33 +0800 Subject: [PATCH 17/96] [add] using a process as an API Guard --- table/process.go | 13 +------------ table/table.go | 22 +++------------------- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/table/process.go b/table/process.go index 10bc0e20..985d8971 100644 --- a/table/process.go +++ b/table/process.go @@ -35,7 +35,6 @@ func ProcessSearch(process *gou.Process) interface{} { table := Select(name) api := table.APIs["search"].ValidateLoop("xiang.table.search") - table.APIGuard(api.Guard, process.Sid, process.Global) // if process.NumOfArgsIs(5) && api.IsAllow(process.Args[4]) { // return nil @@ -69,7 +68,6 @@ func ProcessFind(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["find"].ValidateLoop("xiang.table.find") - table.APIGuard(api.Guard, process.Sid, process.Global) // Before Hook process.Args = table.Before(table.Hooks.BeforeFind, process.Args, process.Sid) @@ -98,7 +96,6 @@ func ProcessSave(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["save"].ValidateLoop("xiang.table.save") - table.APIGuard(api.Guard, process.Sid, process.Global) // Before Hook process.Args = table.Before(table.Hooks.BeforeSave, process.Args, process.Sid) @@ -123,7 +120,6 @@ func ProcessDelete(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["delete"].ValidateLoop("xiang.table.delete") - table.APIGuard(api.Guard, process.Sid, process.Global) id := process.Args[1] return gou.NewProcess(api.Process, id). @@ -140,7 +136,6 @@ func ProcessDeleteWhere(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["delete-where"].ValidateLoop("xiang.table.DeleteWhere") - table.APIGuard(api.Guard, process.Sid, process.Global) // 批量删除 param := api.MergeDefaultQueryParam(process.ArgsQueryParams(1), 0, process.Sid) @@ -162,7 +157,6 @@ func ProcessDeleteIn(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["delete-in"].ValidateLoop("xiang.table.DeleteIn") - table.APIGuard(api.Guard, process.Sid, process.Global) // 批量删除 ids := strings.Split(process.ArgsString(1), ",") @@ -187,7 +181,6 @@ func ProcessUpdateWhere(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["update-where"].ValidateLoop("xiang.table.UpdateWhere") - table.APIGuard(api.Guard, process.Sid, process.Global) // 批量更新 param := api.MergeDefaultQueryParam(process.ArgsQueryParams(1), 0, process.Sid) @@ -208,7 +201,6 @@ func ProcessUpdateIn(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["update-in"].ValidateLoop("xiang.table.UpdateIn") - table.APIGuard(api.Guard, process.Sid, process.Global) // 批量删除 ids := strings.Split(process.ArgsString(1), ",") @@ -231,7 +223,7 @@ func ProcessInsert(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["insert"].ValidateLoop("xiang.table.Insert") - table.APIGuard(api.Guard, process.Sid, process.Global) + return gou.NewProcess(api.Process, process.Args[1:]...). WithGlobal(process.Global). WithSID(process.Sid).Run() @@ -245,7 +237,6 @@ func ProcessSetting(process *gou.Process) interface{} { field := process.ArgsString(1) table := Select(name) api := table.APIs["setting"] - table.APIGuard(api.Guard, process.Sid, process.Global) fields := strings.Split(field, ",") if api.ProcessIs("xiang.table.Setting") { @@ -292,7 +283,6 @@ func ProcessQuickSave(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["quicksave"].ValidateLoop("xiang.table.quicksave") - table.APIGuard(api.Guard, process.Sid, process.Global) args := []interface{}{} payload := process.ArgsMap(1) @@ -324,7 +314,6 @@ func ProcessSelect(process *gou.Process) interface{} { name := process.ArgsString(0) table := Select(name) api := table.APIs["select"].ValidateLoop("xiang.table.select") - table.APIGuard(api.Guard, process.Sid, process.Global) // Before Hook process.Args = table.Before(table.Hooks.BeforeSelect, process.Args, process.Sid) diff --git a/table/table.go b/table/table.go index b98347bb..cb3ec6b6 100644 --- a/table/table.go +++ b/table/table.go @@ -125,9 +125,9 @@ func (table *Table) loadAPIs() { api.Process = table.APIs[name].Process } - // if table.APIs[name].Guard != "" { - // api.Guard = table.APIs[name].Guard - // } + if table.APIs[name].Guard != "" { + api.Guard = table.APIs[name].Guard + } if table.APIs[name].Default != nil { // fmt.Printf("\n%s.APIs[%s].Default: entry\n", table.Table, name) @@ -209,22 +209,6 @@ func (table *Table) After(process string, data interface{}, args []interface{}, return response } -// APIGuard API鉴权 -func (table *Table) APIGuard(guard string, sid string, global map[string]interface{}, args ...interface{}) { - if guard == "" { - guard = table.Guard - } - if guard == "-" || guard == "" { - return - } - - log.With(log.F{"args": args}).Debug(guard) - gou.NewProcess(guard, args...). - WithSID(sid). - WithGlobal(global). - Run() -} - // loadFilters 加载查询过滤器 func (table *Table) loadFilters() { if table.Bind.Model == "" { From 2a25eec809b1457fbf394066b58d9aa34c8bbd1c Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 30 Mar 2022 23:21:17 +0800 Subject: [PATCH 18/96] [CD] remove github token --- .github/workflows/release-linux.yml | 2 -- .github/workflows/release-macos.yml | 2 -- 2 files changed, 4 deletions(-) diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index df49c894..4f8068b3 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -55,7 +55,6 @@ jobs: with: repository: yaoapp/gou path: gou - token: ${{ secrets.TOKEN }} - name: Checkout V8Go uses: actions/checkout@v2 @@ -69,7 +68,6 @@ jobs: with: repository: yaoapp/xgen path: ui - token: ${{ secrets.TOKEN }} - name: Move Kun, Xun, Gou, UI, V8Go run: | diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 8deb4586..507c5139 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -47,7 +47,6 @@ jobs: with: repository: yaoapp/gou path: gou - token: ${{ secrets.TOKEN }} - name: Checkout V8Go uses: actions/checkout@v2 @@ -61,7 +60,6 @@ jobs: with: repository: yaoapp/xgen path: ui - token: ${{ secrets.TOKEN }} - name: Move Kun, Xun, Gou, UI, V8Go run: | From 47c52fba3239cd238e684729dca8766775c1cc55 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 31 Mar 2022 22:11:40 +0800 Subject: [PATCH 19/96] [add] [add] table API custom guard --- Makefile | 2 +- data/bindata.go | 62 ++++++++++++++++++++-------------------- service/guard.go | 2 ++ table/table.go | 48 +++++++++++++++++++++++++++++++ yao/apis/table.http.json | 2 +- 5 files changed, 83 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 0d03f4d3..b144596a 100644 --- a/Makefile +++ b/Makefile @@ -193,7 +193,7 @@ release: clean # Building UI git clone https://github.com/YaoApp/xgen.git .tmp/ui sed -ie "s/url('\/icon/url('\/xiang\/icon/g" .tmp/ui/public/icon/md_icon.css - cd .tmp/ui && npm install && npm run build + cd .tmp/ui && yarn install && yarn build # Packing mkdir -p .tmp/data diff --git a/data/bindata.go b/data/bindata.go index 307356b6..6c319133 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -208,7 +208,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 22, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 22, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -228,7 +228,7 @@ func yaoDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -248,7 +248,7 @@ func yaoApisReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -268,7 +268,7 @@ func yaoApisChartHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/chart.http.json", size: 624, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/apis/chart.http.json", size: 624, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -288,7 +288,7 @@ func yaoApisImportHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/import.http.json", size: 1676, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/apis/import.http.json", size: 1676, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -308,7 +308,7 @@ func yaoApisPageHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/page.http.json", size: 609, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/apis/page.http.json", size: 609, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -328,12 +328,12 @@ func yaoApisStorageHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/storage.http.json", size: 1092, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/apis/storage.http.json", size: 1092, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoApisTableHttpJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x56\xcd\x6e\xd3\x40\x10\xbe\xe7\x29\x56\xab\x1e\x53\x3b\x70\xf4\x0d\x89\x1f\xe5\x44\x51\x41\x1c\xaa\x1e\xa6\xde\x21\x99\xd6\x5e\x6f\xf7\x27\xc1\x54\x79\x04\xe0\xc4\x1d\x84\x90\x38\x21\x1e\x80\xd7\x21\xe2\x31\xd0\xae\x4d\x52\x87\xa4\x0d\xb1\x90\xd3\x4b\xa2\x9d\x99\x6f\x34\xdf\xf7\x79\xec\xbd\xea\x31\xc6\x25\xe4\xc8\x13\xc6\xe7\x1f\xbe\xcf\xdf\x7e\xfb\xf5\xe9\xeb\xfc\xe3\x8f\xf9\xbb\x2f\x3f\xdf\x7f\xe6\x7d\x9f\x9f\xa0\x36\x54\x48\x5f\x72\x2f\x1a\x44\x83\x2a\x2a\xd0\xa4\x9a\x94\xad\x33\x7f\x83\x1f\x1c\x0d\xab\xca\x91\x2e\x9c\xf2\x35\xaf\x09\xe4\x28\xb6\x70\x96\x61\x9d\x71\xa0\x85\xcf\x9c\x21\x68\xd4\x87\xe7\x53\x5b\x25\x14\xd8\xb1\xe1\x09\x3b\xe9\x31\xc6\xd8\x55\xf8\xad\xc3\xbe\x3c\x4e\xfc\xcc\xb1\x41\xd0\xe9\x38\x20\x42\x3a\x47\x3b\x2e\x42\xbf\x27\x8f\x9e\x2f\xc3\x4a\x17\x29\x1a\xb3\x98\x20\x0a\x13\x44\xc7\x2b\x68\xf2\x3c\x4e\xf8\x81\x02\x0d\x79\x14\x44\xe9\x33\x9e\x5c\x3a\xd4\xe5\x61\x08\xfa\xf3\x41\x38\x47\x0a\x46\xb8\x72\x34\xf4\x06\xf9\xe9\xa2\x5d\xe1\x2c\x4f\x16\xa3\x33\xc6\x8d\x05\xeb\xfc\x14\xf7\x07\x83\xfe\x32\x6c\x4b\x15\xd4\x07\xa5\x32\x4a\xc1\xeb\x19\x9f\x9b\x42\xf2\xba\x64\x16\xfe\x67\xfd\x1b\x95\x78\x45\x52\xc4\x09\x89\x5d\xb5\x78\x4c\x52\xdc\xa6\x44\x7d\x24\xd1\x15\x49\x83\x19\xa6\x76\x77\xbb\x9b\xe8\x8a\xe4\x72\xc4\x06\xdb\x6b\xe1\xca\xe0\x0b\x2c\xa7\x85\x16\x6b\x32\x1b\x00\x13\xc8\xdc\xba\x78\x46\x39\xd9\x3f\xb4\xbb\x12\x12\x26\xb8\x4e\xc6\xa3\xa7\xc7\x5b\xe8\xd8\x00\x6f\x58\x1a\x05\x65\x56\x40\x67\x4f\xca\xa5\xa3\xf4\xa2\x15\xcb\x67\xbe\xc3\x5d\xa0\x2a\x30\x43\x8b\x9b\x76\x7f\x2b\xae\x0f\x43\x8b\xbd\x5f\x7f\x92\x06\xf5\xda\xf5\xdf\x8a\xe5\x70\x05\xbe\x91\x65\xb0\x33\x4a\x8b\xcc\xe5\xd2\x34\x62\x61\xa7\x4d\xc7\x4e\x4f\xc7\xa8\x77\x7f\xae\x2b\xaf\x5f\x36\x7b\x6c\xf1\xe5\xeb\x96\x33\xc9\x96\x84\x87\xf2\x56\xe3\xab\xd7\x33\x09\x73\xfd\xb3\xae\x29\x07\x5d\x76\xc5\xde\x29\x01\xad\x1d\x7f\x11\x9a\xfc\xbb\xe3\x7b\xf0\x6e\xab\xf9\xb7\x70\xbf\x22\xdf\xd2\xfd\x3d\x50\xc2\xa0\xb5\x24\x47\xbb\xdf\x7d\x56\xe0\x37\xaa\x50\xdf\xb3\xfe\x1b\xd5\x1e\x63\xa7\xbd\x59\xef\x77\x00\x00\x00\xff\xff\x8a\x3e\xea\x17\x7c\x0c\x00\x00") +var _yaoApisTableHttpJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x56\xcb\x6e\xd3\x40\x14\xdd\xfb\x2b\x46\xa3\x2e\x53\x3b\xb0\xf4\x0e\x89\x87\xb2\xa2\xa8\x20\x16\x55\x17\x17\xcf\x25\xb9\xd4\x1e\x4f\xe7\x91\x62\xaa\x7c\x02\xb0\x62\x0f\x42\x48\xac\x10\x1f\xc0\xef\x10\xf1\x19\x68\xc6\x26\xa9\x43\xd2\x86\x58\xc8\xe9\x26\xd1\x9c\xfb\xf0\x3d\xe7\xf8\xda\xbe\x8c\x18\xe3\x12\x0a\xe4\x29\xe3\xf3\x0f\xdf\xe7\x6f\xbf\xfd\xfa\xf4\x75\xfe\xf1\xc7\xfc\xdd\x97\x9f\xef\x3f\xf3\x81\x8f\x4f\x51\x1b\x2a\xa5\x4f\xb9\x13\x0f\xe3\x61\x8d\x0a\x34\x99\x26\x65\x9b\xc8\xdf\xc5\xf7\x8e\x46\x75\xe6\x58\x97\x4e\xf9\x9c\xd7\x04\x72\x9c\x58\x78\x91\x63\x13\x71\xa0\x85\x8f\x04\xec\xb0\x3e\x86\x88\x02\x3b\x31\x3c\x65\x27\x11\x63\x8c\x5d\x86\xdf\x06\xf6\xf9\x49\xea\x87\x4e\x0c\x82\xce\x26\xa1\x22\x84\x0b\xb4\x93\x32\x34\x7c\xf4\xe0\xe9\x12\x56\xba\xcc\xd0\x98\xc5\x08\x71\xb8\x5c\x7c\xbc\x52\x4d\x9e\xc8\x09\x3f\x50\xa0\xa1\x88\x83\x2a\x03\xc6\xd3\x73\x87\xba\x3a\x0c\xa0\x3f\x1f\x84\x73\xac\x60\x8c\x2b\x47\x43\x6f\x90\x9f\x2e\xda\x95\xce\xf2\x74\x31\x3a\x63\xdc\x58\xb0\xce\x4f\x71\x77\x38\x1c\x2c\x61\x5b\xa9\x20\x3f\x28\x95\x53\x06\x5e\xd0\xe4\x95\x29\x25\x6f\x52\x66\xe1\x7f\x36\xb8\x56\x89\x97\x24\x45\x92\x92\xd8\x55\x8b\x87\x24\xc5\x4d\x4a\x34\x47\x12\x7d\x91\x34\x98\x63\x66\x77\xb7\xbb\x5d\x5d\x93\x5c\x8e\xd8\x62\x7b\x05\xae\x0d\x3e\xc3\xea\xa2\xd4\x62\x4d\x64\x43\xc1\x14\x72\xb7\x0e\xcf\xa9\x20\xfb\x87\x76\x5f\x42\xc2\x14\xd7\xc9\x78\xf4\xf8\x78\x0b\x1d\x5b\xc5\x1b\x96\x46\x41\x95\x97\xd0\xdb\x9d\x72\xee\x28\x3b\xeb\xc4\xf2\x89\xef\x70\x1b\xa8\x0a\xcc\xd1\xe2\xa6\xdd\xdf\x8a\xeb\xfd\xd0\x62\xef\xd7\x9f\xa4\x41\xbd\x76\xfd\xb7\x62\x39\x5a\x29\xdf\xc8\x32\xd8\x19\x67\x65\xee\x0a\x69\x5a\x58\xd8\x69\xd3\xb3\xd3\x17\x13\xd4\xbb\xdf\xd7\xb5\xd7\xcf\xdb\x3d\xb6\x78\xf3\xf5\xcb\x99\x64\x47\xc2\x23\x79\xa3\xf1\xf5\xe3\x99\x84\xb9\xfa\x5a\xd7\x54\x80\xae\xfa\x62\xef\x94\x80\xce\x8e\x3f\x0b\x4d\xfe\xdd\xf1\x3d\x78\xb6\x35\xfc\x3b\xb8\x5f\x93\xef\xe8\xfe\x1e\x28\x61\xd0\x5a\x92\xe3\xdd\xbf\x7d\x56\xca\xaf\x55\xa1\xf9\xce\xfa\x6f\x54\x23\xc6\x4e\xa3\x59\xf4\x3b\x00\x00\xff\xff\x26\x8b\x17\x1d\x7d\x0c\x00\x00") func yaoApisTableHttpJsonBytes() ([]byte, error) { return bindataRead( @@ -348,7 +348,7 @@ func yaoApisTableHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/table.http.json", size: 3196, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/apis/table.http.json", size: 3197, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -368,7 +368,7 @@ func yaoApisUserHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/user.http.json", size: 1453, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/apis/user.http.json", size: 1453, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -388,7 +388,7 @@ func yaoApisXiangHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/xiang.http.json", size: 1239, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/apis/xiang.http.json", size: 1239, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -408,7 +408,7 @@ func yaoDataDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/data/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -428,7 +428,7 @@ func yaoDataAppJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/app.json", size: 277, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/data/app.json", size: 277, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -448,7 +448,7 @@ func yaoDataIconsDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/data/icons/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -468,7 +468,7 @@ func yaoDataIcons404Png() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -488,7 +488,7 @@ func yaoDataIconsIconIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -508,7 +508,7 @@ func yaoDataIconsIconIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -528,7 +528,7 @@ func yaoDataIconsIconPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -548,7 +548,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -568,7 +568,7 @@ func yaoFlowsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -588,7 +588,7 @@ func yaoFlowsAdminGuardFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/admin/guard.flow.json", size: 559, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/flows/admin/guard.flow.json", size: 559, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -608,7 +608,7 @@ func yaoFlowsMenuFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -628,7 +628,7 @@ func yaoFlowsUserFindFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/user/find.flow.json", size: 328, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/flows/user/find.flow.json", size: 328, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -648,7 +648,7 @@ func yaoLangsEnUsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -668,7 +668,7 @@ func yaoModelsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -688,7 +688,7 @@ func yaoModelsMenuModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/menu.mod.json", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/models/menu.mod.json", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -708,7 +708,7 @@ func yaoModelsUserModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user.mod.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -728,7 +728,7 @@ func yaoModelsWorkflowModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/workflow.mod.json", size: 3333, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/models/workflow.mod.json", size: 3333, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -748,7 +748,7 @@ func yaoTablesReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -768,7 +768,7 @@ func yaoTablesMenuTabJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/menu.tab.json", size: 10471, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/tables/menu.tab.json", size: 10471, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -788,7 +788,7 @@ func yaoTablesUserTabJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/user.tab.json", size: 2357, mode: os.FileMode(420), modTime: time.Unix(1646123553, 0)} + info := bindataFileInfo{name: "yao/tables/user.tab.json", size: 2357, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/service/guard.go b/service/guard.go index ce187a3a..a8f2263a 100644 --- a/service/guard.go +++ b/service/guard.go @@ -5,12 +5,14 @@ import ( "github.com/gin-gonic/gin" "github.com/yaoapp/yao/helper" + "github.com/yaoapp/yao/table" ) // Guards 服务中间件 var Guards = map[string]gin.HandlerFunc{ "bearer-jwt": bearerJWT, // JWT 鉴权 "cross-domain": crossDomain, // 跨域许可 + "table-guard": table.Guard, // Table Guard } // JWT 鉴权 diff --git a/table/table.go b/table/table.go index cb3ec6b6..dd296c71 100644 --- a/table/table.go +++ b/table/table.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/gin-gonic/gin" "github.com/yaoapp/gou" "github.com/yaoapp/gou/helper" "github.com/yaoapp/kun/exception" @@ -18,6 +19,52 @@ import ( // Tables 已载入模型 var Tables = map[string]*Table{} +// Guard Table guard +func Guard(c *gin.Context) { + + if !strings.HasPrefix(c.FullPath(), "/api/xiang/table") { + c.Next() + return + } + + routes := strings.Split(c.FullPath(), "/") + path := routes[len(routes)-1] + name, has := c.Params.Get("name") + if !has { + c.Next() + return + } + + table, has := Tables[name] + if !has { + c.Next() + return + } + + api, has := table.APIs[path] + if !has { + c.Next() + return + } + + if api.Guard == "-" { + c.Next() + return + } + + fmt.Println("Table Guard:", name, api.Guard) + + if middleware, has := gou.HTTPGuards[api.Guard]; has { + middleware(c) + return + } + + if api.Guard != "" { + gou.ProcessGuard(api.Guard)(c) + return + } +} + // Load 加载数据表格 func Load(cfg config.Config) error { if share.BUILDIN { @@ -125,6 +172,7 @@ func (table *Table) loadAPIs() { api.Process = table.APIs[name].Process } + api.Guard = "bearer-jwt" if table.APIs[name].Guard != "" { api.Guard = table.APIs[name].Guard } diff --git a/yao/apis/table.http.json b/yao/apis/table.http.json index 8b435035..a22774ee 100644 --- a/yao/apis/table.http.json +++ b/yao/apis/table.http.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "数据表格接口API", "group": "xiang/table", - "guard": "bearer-jwt", + "guard": "table-guard", "paths": [ { "path": "/:name/search", From 46ae05a6a8315bf186f403fe7db3f72060e21124 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 2 Apr 2022 09:33:51 +0800 Subject: [PATCH 20/96] [add] Table API multi guards --- table/table.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/table/table.go b/table/table.go index dd296c71..76305fbd 100644 --- a/table/table.go +++ b/table/table.go @@ -52,16 +52,15 @@ func Guard(c *gin.Context) { return } - fmt.Println("Table Guard:", name, api.Guard) - - if middleware, has := gou.HTTPGuards[api.Guard]; has { - middleware(c) - return - } - - if api.Guard != "" { - gou.ProcessGuard(api.Guard)(c) - return + guards := strings.Split(api.Guard, ",") + for _, guard := range guards { + guard = strings.TrimSpace(guard) + log.Trace("Guard Table %s %s", name, guard) + if middleware, has := gou.HTTPGuards[guard]; has { + middleware(c) + continue + } + gou.ProcessGuard(guard)(c) } } From bf0405d2c47c578043ed0415b486c86a9d817dde Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 2 Apr 2022 09:55:49 +0800 Subject: [PATCH 21/96] [fix] table.Guard bug --- table/table.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/table/table.go b/table/table.go index 76305fbd..f43001a5 100644 --- a/table/table.go +++ b/table/table.go @@ -52,15 +52,21 @@ func Guard(c *gin.Context) { return } + if api.Guard == "" { + api.Guard = "bearer-jwt" + } + guards := strings.Split(api.Guard, ",") for _, guard := range guards { guard = strings.TrimSpace(guard) log.Trace("Guard Table %s %s", name, guard) - if middleware, has := gou.HTTPGuards[guard]; has { - middleware(c) - continue + if guard != "" { + if middleware, has := gou.HTTPGuards[guard]; has { + middleware(c) + continue + } + gou.ProcessGuard(guard)(c) } - gou.ProcessGuard(guard)(c) } } From 098f1b53ce088b8ebeb63309f9ad88be48b26444 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 2 Apr 2022 11:55:39 +0800 Subject: [PATCH 22/96] [add] cross domain env --- config/types.go | 5 +++-- service/service.go | 14 ++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/config/types.go b/config/types.go index 33d6f6f0..123d7c37 100644 --- a/config/types.go +++ b/config/types.go @@ -11,8 +11,9 @@ type Config struct { Log string `json:"log,omitempty" env:"YAO_LOG"` // 服务日志地址 LogMode string `json:"log_mode,omitempty" env:"YAO_LOG_MODE" envDefault:"TEXT"` // 服务日志模式 JSON|TEXT // Session string `json:"session,omitempty" env:"YAO_SESSION" envDefault:"memory"` // 用户会话模式 memory|redis|database - JWTSecret string `json:"jwt_secret,omitempty" env:"YAO_JWT_SECRET"` // JWT 密钥 - DB DBConfig `json:"db,omitempty"` // 数据库配置 + JWTSecret string `json:"jwt_secret,omitempty" env:"YAO_JWT_SECRET"` // JWT 密钥 + DB DBConfig `json:"db,omitempty"` // 数据库配置 + AllowFrom []string `json:"allowfrom,omitempty" envSeparator:"|" env:"YAO_ALLOW_FROM" ` // Domain list the separator is | Session SessionConfig `json:"session,omitempty"` } diff --git a/service/service.go b/service/service.go index 304d1e1b..51858b99 100644 --- a/service/service.go +++ b/service/service.go @@ -19,9 +19,10 @@ func Start() { gou.SetHTTPGuards(Guards) gou.ServeHTTP( gou.Server{ - Host: config.Conf.Host, - Port: config.Conf.Port, - Root: "/api", + Host: config.Conf.Host, + Port: config.Conf.Port, + Root: "/api", + Allows: config.Conf.AllowFrom, }, &shutdown, func(s gou.Server) { shutdownComplete <- true @@ -35,9 +36,10 @@ func StartWithouttSession() { gou.SetHTTPGuards(Guards) gou.ServeHTTP( gou.Server{ - Host: config.Conf.Host, - Port: config.Conf.Port, - Root: "/api", + Host: config.Conf.Host, + Port: config.Conf.Port, + Root: "/api", + Allows: config.Conf.AllowFrom, }, &shutdown, func(s gou.Server) { shutdownComplete <- true From a21995c3572948015ef8b94d2791bc87cf8aa1ba Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 2 Apr 2022 14:38:56 +0800 Subject: [PATCH 23/96] [debug] cross domain --- service/guard.go | 3 --- table/table.go | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/service/guard.go b/service/guard.go index a8f2263a..63a3d16a 100644 --- a/service/guard.go +++ b/service/guard.go @@ -27,7 +27,6 @@ func bearerJWT(c *gin.Context) { claims := helper.JwtValidate(tokenString) c.Set("__sid", claims.SID) - c.Next() } // crossDomain 跨域访问 @@ -41,6 +40,4 @@ func crossDomain(c *gin.Context) { c.AbortWithStatus(204) return } - - c.Next() } diff --git a/table/table.go b/table/table.go index f43001a5..4121c3a5 100644 --- a/table/table.go +++ b/table/table.go @@ -68,6 +68,7 @@ func Guard(c *gin.Context) { gou.ProcessGuard(guard)(c) } } + } // Load 加载数据表格 From 95e36f09a13f781117195ea9161559bf5095de97 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 7 Apr 2022 16:59:55 +0800 Subject: [PATCH 24/96] fix process guard bug --- api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api.go b/api/api.go index 7da8627c..0de0ed78 100644 --- a/api/api.go +++ b/api/api.go @@ -26,7 +26,7 @@ func LoadFrom(dir string, prefix string) error { err := share.Walk(dir, ".http.json", func(root, filename string) { name := prefix + share.SpecName(root, filename) content := share.ReadFile(filename) - _, err := gou.LoadAPIReturn(string(content), name) + _, err := gou.LoadAPIReturn(string(content), name, "bearer-jwt") if err != nil { log.With(log.F{"root": root, "file": filename}).Error(err.Error()) } From d8d4bd7ceec8c6b31d39d0fabed4b782d62a092c Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 8 Apr 2022 17:15:33 +0800 Subject: [PATCH 25/96] Fix import xlsx chunk bug --- importer/xlsx/xlsx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/importer/xlsx/xlsx.go b/importer/xlsx/xlsx.go index 378adcaa..5b2ee80c 100644 --- a/importer/xlsx/xlsx.go +++ b/importer/xlsx/xlsx.go @@ -95,7 +95,7 @@ func (xlsx *Xlsx) Chunk(size int, axises []string, cb func(line int, data [][]in row, end := xlsx.readLine(line, axises) if end { cb(line, data) - break + return } data = append(data, row) From c775df16f4a75baff3554ac98e5f184aee2afe72 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 10 Apr 2022 17:29:37 +0800 Subject: [PATCH 26/96] Fix FreePort DNS deps & Session bug --- engine/load.go | 4 ++-- go.mod | 2 +- go.sum | 8 +++---- network/ip.go | 3 ++- share/session.go | 54 +++++++++++++++++++++++++++--------------------- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/engine/load.go b/engine/load.go index bef0cdd1..40b3568f 100644 --- a/engine/load.go +++ b/engine/load.go @@ -39,8 +39,8 @@ func Load(cfg config.Config) (err error) { gou.LoadCrypt(`{}`, "PASSWORD") // 第二步: 建立数据库 & 会话连接 - share.DBConnect(cfg.DB) // 创建数据库连接 - share.SessionConnect(cfg.Session) // 创建会话服务器链接 + share.DBConnect(cfg.DB) // 创建数据库连接 + // share.SessionConnect(cfg.Session) // 创建会话服务器链接 // 加载应用引擎 if os.Getenv("YAO_DEV") != "" { diff --git a/go.mod b/go.mod index 6405a23d..c12b3db3 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/armon/go-metrics v0.3.10 // indirect github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/buraksezer/consistent v0.9.0 // indirect - github.com/buraksezer/olric v0.4.2 + github.com/buraksezer/olric v0.4.3 github.com/caarlos0/env/v6 v6.9.1 github.com/elazarl/go-bindata-assetfs v1.0.1 github.com/fatih/color v1.13.0 diff --git a/go.sum b/go.sum index 90abbb39..e9a42c47 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/buraksezer/connpool v0.4.0/go.mod h1:qPiG7gKXo+EjrwG/yqn2StZM4ek6gcYn github.com/buraksezer/consistent v0.0.0-20191006190839-693edf70fd72/go.mod h1:OEE5igu/CDjGegM1Jn6ZMo7R6LlV/JChAkjfQQIRLpg= github.com/buraksezer/consistent v0.9.0 h1:Zfs6bX62wbP3QlbPGKUhqDw7SmNkOzY5bHZIYXYpR5g= github.com/buraksezer/consistent v0.9.0/go.mod h1:6BrVajWq7wbKZlTOUPs/XVfR8c0maujuPowduSpZqmw= -github.com/buraksezer/olric v0.4.2 h1:1W5UCYFYrkeD6OHGyTi6R9YTLJ3vXcVkgas4ZbFQMfw= -github.com/buraksezer/olric v0.4.2/go.mod h1:xNt+/QiiVuqioJzgTMuiV2LynTNu87L1Tp5289XuU78= +github.com/buraksezer/olric v0.4.3 h1:A8QKRv/2BiIOWmoCzbsRniW/zbuN80vAJXMfEo4N7qc= +github.com/buraksezer/olric v0.4.3/go.mod h1:xNt+/QiiVuqioJzgTMuiV2LynTNu87L1Tp5289XuU78= github.com/caarlos0/env/v6 v6.9.1 h1:zOkkjM0F6ltnQ5eBX6IPI41UP/KDGEK7rRPwGCNos8k= github.com/caarlos0/env/v6 v6.9.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -147,8 +147,6 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= -github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= @@ -207,8 +205,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= diff --git a/network/ip.go b/network/ip.go index 6f1243f3..80f1fc55 100644 --- a/network/ip.go +++ b/network/ip.go @@ -41,7 +41,7 @@ func ProcessIP(process *gou.Process) interface{} { // FreePort xiang.network.FreePort 获取可用端口 func FreePort() int { - addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + addr, err := net.ResolveTCPAddr("tcp", ":0") if err != nil { exception.New("获取可用端口失败 %s", 500, err.Error()).Throw() return 0 @@ -52,6 +52,7 @@ func FreePort() int { exception.New("获取可用端口失败 %s", 500, err.Error()).Throw() return 0 } + defer l.Close() return l.Addr().(*net.TCPAddr).Port } diff --git a/share/session.go b/share/session.go index d47e3e18..b696a553 100644 --- a/share/session.go +++ b/share/session.go @@ -7,15 +7,11 @@ import ( "log" klog "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/network" "github.com/buraksezer/olric" - "github.com/buraksezer/olric/client" config_olric "github.com/buraksezer/olric/config" - "github.com/buraksezer/olric/serializer" "github.com/yaoapp/gou/session" - "github.com/yaoapp/kun/exception" - "github.com/yaoapp/yao/config" - "github.com/yaoapp/yao/network" ) var sessServer *olric.Olric @@ -23,28 +19,35 @@ var sessServer *olric.Olric // SessionPort Session 端口 var SessionPort int +// SessionMemberPort Session Member Port +var SessionMemberPort int + func init() { SessionPort = network.FreePort() - klog.Trace("session port: %d", SessionPort) + SessionMemberPort = network.FreePort() + klog.Trace("Session port: %d, Memeber Port:", SessionPort, SessionMemberPort) } -// SessionConnect 加载会话信息 -func SessionConnect(conf config.SessionConfig) { +// SessionConnect 加载会话信息 (废弃->共享方案用 Redis 替代) +// func SessionConnect() { - var clientConfig = &client.Config{ - Servers: []string{fmt.Sprintf("%s:%d", "127.0.0.1", SessionPort)}, - Serializer: serializer.NewMsgpackSerializer(), - Client: config_olric.NewClient(), - } +// stats, err := sessServer.Stats() +// fmt.Println(stats, err) - c, err := client.New(clientConfig) - if err != nil { - exception.New("会话服务器连接失败 %s", 500, err.Error()).Throw() - } +// var clientConfig = &client.Config{ +// Servers: []string{fmt.Sprintf("%s:%d", "127.0.0.1", SessionPort)}, +// Serializer: serializer.NewMsgpackSerializer(), +// Client: config_olric.NewClient(), +// } - dm := c.NewDMap("local-session") - session.MemoryUse(session.ClientDMap{DMap: dm}) -} +// c, err := client.New(clientConfig) +// if err != nil { +// exception.New("会话服务器连接失败 %s", 500, err.Error()).Throw() +// } + +// dm := c.NewDMap("local-session") +// session.MemoryUse(session.ClientDMap{DMap: dm}) +// } // SessionServerStop 关闭会话服务器 func SessionServerStop() { @@ -56,6 +59,7 @@ func SessionServerStop() { // SessionServerStart 启动会话服务器 func SessionServerStart() { + // SessionPort := 0 c := &config_olric.Config{ BindAddr: "127.0.0.1", BindPort: SessionPort, @@ -74,9 +78,10 @@ func SessionServerStart() { if err != nil { panic(fmt.Sprintf("unable to create a new memberlist config: %v", err)) } - // m.BindAddr = config.Conf.Session.Host - m.BindPort = SessionPort - m.AdvertisePort = SessionPort + m.BindAddr = "127.0.0.1" + m.BindPort = SessionMemberPort + // m.AdvertiseAddr = "127.0.0.1" + // m.AdvertisePort = SessionPort c.MemberlistConfig = m // c.MemberlistConfig.BindAddr = config.Conf.Session.Host @@ -94,14 +99,15 @@ func SessionServerStart() { } sessServer, err = olric.New(c) - if err != nil { klog.Error("Failed to create Olric instance: %v", err) } go func() { + // fmt.Println("Session Port IS:", SessionPort) // DEBUG err = sessServer.Start() // Call Start at background. It's a blocker call. if err != nil { + fmt.Println(err) klog.Panic("olric.Start returned an error: %v", err) } }() From 77d49483fb4bc2a987eb801842dcde7bf49caeed Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 10 Apr 2022 17:34:35 +0800 Subject: [PATCH 27/96] fix session init log bug --- share/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/session.go b/share/session.go index b696a553..974282e5 100644 --- a/share/session.go +++ b/share/session.go @@ -25,7 +25,7 @@ var SessionMemberPort int func init() { SessionPort = network.FreePort() SessionMemberPort = network.FreePort() - klog.Trace("Session port: %d, Memeber Port:", SessionPort, SessionMemberPort) + klog.Trace("Session port: %d, Memeber Port: %d", SessionPort, SessionMemberPort) } // SessionConnect 加载会话信息 (废弃->共享方案用 Redis 替代) From 89b8fbfdcc08ce97a7b73767e6cfa0eeec47f8b3 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 10 Apr 2022 18:11:52 +0800 Subject: [PATCH 28/96] fix misspelling --- share/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/session.go b/share/session.go index 974282e5..68189f9c 100644 --- a/share/session.go +++ b/share/session.go @@ -25,7 +25,7 @@ var SessionMemberPort int func init() { SessionPort = network.FreePort() SessionMemberPort = network.FreePort() - klog.Trace("Session port: %d, Memeber Port: %d", SessionPort, SessionMemberPort) + klog.Trace("Session port: %d, Member Port: %d", SessionPort, SessionMemberPort) } // SessionConnect 加载会话信息 (废弃->共享方案用 Redis 替代) From 61a06735507383a1c694d85800d7b546aa4a8db8 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 10 Apr 2022 19:16:12 +0800 Subject: [PATCH 29/96] fix unit test --- cmd/run.go | 2 ++ engine/user_test.go | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/cmd/run.go b/cmd/run.go index 28e0a141..60e617b0 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -20,6 +20,8 @@ var runCmd = &cobra.Command{ Short: L("Execute process"), Long: L("Execute process"), Run: func(cmd *cobra.Command, args []string) { + share.SessionServerStart() // Connect to session server + defer share.SessionServerStop() // Connect to session server defer gou.KillPlugins() defer func() { err := exception.Catch(recover()) diff --git a/engine/user_test.go b/engine/user_test.go index 4a05b208..1d86e97c 100644 --- a/engine/user_test.go +++ b/engine/user_test.go @@ -4,10 +4,16 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/yaoapp/yao/share" "github.com/yaoapp/yao/user" ) func TestUserAuth(t *testing.T) { + + // Start Session Server + share.SessionServerStart() + defer share.SessionServerStop() + res := user.Auth("email", "xiang@iqka.com", "A123456p+") assert.True(t, res.Has("user")) assert.True(t, res.Has("token")) From 3a4af45bcf0ac2da560e4431f4bc6cddc5d7892e Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 13 Apr 2022 09:27:40 +0800 Subject: [PATCH 30/96] Optimize release workflow --- .github/workflows/release-linux.yml | 28 ++++++++++++++-------------- .github/workflows/release-macos.yml | 22 +++++++++++----------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 4f8068b3..85a537ab 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -108,19 +108,19 @@ jobs: run: | coscmd config -a $SECRET_ID -s $SECRET_KEY -b $BUCKET -r $REGION - - name: Push To Silicon Valley - run: | - for file in ./dist/release/*; do coscmd upload $file /archives/; done; + # - name: Push To Silicon Valley + # run: | + # for file in ./dist/release/*; do coscmd upload $file /archives/; done; - - name: Configure COS For Beijing - env: - SECRET_ID: ${{ secrets.COS_ID }} - SECRET_KEY: ${{ secrets.COS_KEY }} - BUCKET: release-bj-1252011659 - REGION: ap-beijing - run: | - coscmd config -a $SECRET_ID -s $SECRET_KEY -b $BUCKET -r $REGION + # - name: Configure COS For Beijing + # env: + # SECRET_ID: ${{ secrets.COS_ID }} + # SECRET_KEY: ${{ secrets.COS_KEY }} + # BUCKET: release-bj-1252011659 + # REGION: ap-beijing + # run: | + # coscmd config -a $SECRET_ID -s $SECRET_KEY -b $BUCKET -r $REGION - - name: Push To Beijing - run: | - for file in ./dist/release/*; do coscmd upload $file /archives/; done; + # - name: Push To Beijing + # run: | + # for file in ./dist/release/*; do coscmd upload $file /archives/; done; diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 507c5139..ee21abf5 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -104,15 +104,15 @@ jobs: run: | for file in ./dist/release/*; do coscmd upload $file /archives/; done; - - name: Configure COS For Beijing - env: - SECRET_ID: ${{ secrets.COS_ID }} - SECRET_KEY: ${{ secrets.COS_KEY }} - BUCKET: release-bj-1252011659 - REGION: ap-beijing - run: | - coscmd config -a $SECRET_ID -s $SECRET_KEY -b $BUCKET -r $REGION + # - name: Configure COS For Beijing + # env: + # SECRET_ID: ${{ secrets.COS_ID }} + # SECRET_KEY: ${{ secrets.COS_KEY }} + # BUCKET: release-bj-1252011659 + # REGION: ap-beijing + # run: | + # coscmd config -a $SECRET_ID -s $SECRET_KEY -b $BUCKET -r $REGION - - name: Push To Beijing - run: | - for file in ./dist/release/*; do coscmd upload $file /archives/; done; + # - name: Push To Beijing + # run: | + # for file in ./dist/release/*; do coscmd upload $file /archives/; done; From 486fd702c0eee44a28e84bc548b72eec390116da Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 13 Apr 2022 20:26:07 +0800 Subject: [PATCH 31/96] fix table find API bug --- data/bindata.go | 62 ++++++++++++++++++++-------------------- table/process.go | 2 +- yao/apis/table.http.json | 2 +- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/data/bindata.go b/data/bindata.go index 6c319133..cefc3054 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -208,7 +208,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 22, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 22, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -228,7 +228,7 @@ func yaoDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -248,7 +248,7 @@ func yaoApisReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -268,7 +268,7 @@ func yaoApisChartHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/chart.http.json", size: 624, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/apis/chart.http.json", size: 624, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -288,7 +288,7 @@ func yaoApisImportHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/import.http.json", size: 1676, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/apis/import.http.json", size: 1676, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -308,7 +308,7 @@ func yaoApisPageHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/page.http.json", size: 609, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/apis/page.http.json", size: 609, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -328,12 +328,12 @@ func yaoApisStorageHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/storage.http.json", size: 1092, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/apis/storage.http.json", size: 1092, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoApisTableHttpJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x56\xcb\x6e\xd3\x40\x14\xdd\xfb\x2b\x46\xa3\x2e\x53\x3b\xb0\xf4\x0e\x89\x87\xb2\xa2\xa8\x20\x16\x55\x17\x17\xcf\x25\xb9\xd4\x1e\x4f\xe7\x91\x62\xaa\x7c\x02\xb0\x62\x0f\x42\x48\xac\x10\x1f\xc0\xef\x10\xf1\x19\x68\xc6\x26\xa9\x43\xd2\x86\x58\xc8\xe9\x26\xd1\x9c\xfb\xf0\x3d\xe7\xf8\xda\xbe\x8c\x18\xe3\x12\x0a\xe4\x29\xe3\xf3\x0f\xdf\xe7\x6f\xbf\xfd\xfa\xf4\x75\xfe\xf1\xc7\xfc\xdd\x97\x9f\xef\x3f\xf3\x81\x8f\x4f\x51\x1b\x2a\xa5\x4f\xb9\x13\x0f\xe3\x61\x8d\x0a\x34\x99\x26\x65\x9b\xc8\xdf\xc5\xf7\x8e\x46\x75\xe6\x58\x97\x4e\xf9\x9c\xd7\x04\x72\x9c\x58\x78\x91\x63\x13\x71\xa0\x85\x8f\x04\xec\xb0\x3e\x86\x88\x02\x3b\x31\x3c\x65\x27\x11\x63\x8c\x5d\x86\xdf\x06\xf6\xf9\x49\xea\x87\x4e\x0c\x82\xce\x26\xa1\x22\x84\x0b\xb4\x93\x32\x34\x7c\xf4\xe0\xe9\x12\x56\xba\xcc\xd0\x98\xc5\x08\x71\xb8\x5c\x7c\xbc\x52\x4d\x9e\xc8\x09\x3f\x50\xa0\xa1\x88\x83\x2a\x03\xc6\xd3\x73\x87\xba\x3a\x0c\xa0\x3f\x1f\x84\x73\xac\x60\x8c\x2b\x47\x43\x6f\x90\x9f\x2e\xda\x95\xce\xf2\x74\x31\x3a\x63\xdc\x58\xb0\xce\x4f\x71\x77\x38\x1c\x2c\x61\x5b\xa9\x20\x3f\x28\x95\x53\x06\x5e\xd0\xe4\x95\x29\x25\x6f\x52\x66\xe1\x7f\x36\xb8\x56\x89\x97\x24\x45\x92\x92\xd8\x55\x8b\x87\x24\xc5\x4d\x4a\x34\x47\x12\x7d\x91\x34\x98\x63\x66\x77\xb7\xbb\x5d\x5d\x93\x5c\x8e\xd8\x62\x7b\x05\xae\x0d\x3e\xc3\xea\xa2\xd4\x62\x4d\x64\x43\xc1\x14\x72\xb7\x0e\xcf\xa9\x20\xfb\x87\x76\x5f\x42\xc2\x14\xd7\xc9\x78\xf4\xf8\x78\x0b\x1d\x5b\xc5\x1b\x96\x46\x41\x95\x97\xd0\xdb\x9d\x72\xee\x28\x3b\xeb\xc4\xf2\x89\xef\x70\x1b\xa8\x0a\xcc\xd1\xe2\xa6\xdd\xdf\x8a\xeb\xfd\xd0\x62\xef\xd7\x9f\xa4\x41\xbd\x76\xfd\xb7\x62\x39\x5a\x29\xdf\xc8\x32\xd8\x19\x67\x65\xee\x0a\x69\x5a\x58\xd8\x69\xd3\xb3\xd3\x17\x13\xd4\xbb\xdf\xd7\xb5\xd7\xcf\xdb\x3d\xb6\x78\xf3\xf5\xcb\x99\x64\x47\xc2\x23\x79\xa3\xf1\xf5\xe3\x99\x84\xb9\xfa\x5a\xd7\x54\x80\xae\xfa\x62\xef\x94\x80\xce\x8e\x3f\x0b\x4d\xfe\xdd\xf1\x3d\x78\xb6\x35\xfc\x3b\xb8\x5f\x93\xef\xe8\xfe\x1e\x28\x61\xd0\x5a\x92\xe3\xdd\xbf\x7d\x56\xca\xaf\x55\xa1\xf9\xce\xfa\x6f\x54\x23\xc6\x4e\xa3\x59\xf4\x3b\x00\x00\xff\xff\x26\x8b\x17\x1d\x7d\x0c\x00\x00") +var _yaoApisTableHttpJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x56\xcb\x6e\xd3\x40\x14\xdd\xfb\x2b\x46\xa3\x2e\x53\x3b\xb0\xf4\x0e\x89\x87\xb2\xa2\xa8\x20\x16\x55\x17\x17\xcf\x25\x19\x6a\xcf\x4c\xe7\x91\x62\xaa\x7c\x02\xb0\x62\x0f\x42\x48\xac\x10\x1f\xc0\xef\x10\xf1\x19\x68\xc6\x26\xa9\x4d\xd2\x9a\x58\xc8\xe9\x26\xd1\x3d\xf7\xe1\x7b\xce\xb1\xc7\xbe\x8c\x08\xa1\x02\x0a\xa4\x29\xa1\xcb\x0f\xdf\x97\x6f\xbf\xfd\xfa\xf4\x75\xf9\xf1\xc7\xf2\xdd\x97\x9f\xef\x3f\xd3\x91\xcf\xcf\x51\x1b\x2e\x85\x2f\xb9\x13\x8f\xe3\x71\x85\x32\x34\x99\xe6\xca\xd6\x99\xbf\x9b\xef\x1d\x4d\xaa\xca\xa9\x96\x4e\xf9\x9a\xd7\x1c\xc4\x34\xb1\xf0\x22\xc7\x3a\xe3\x40\x33\x9f\x09\xd8\x61\x15\x86\x8c\x02\x3b\x33\x34\x25\x27\x11\x21\x84\x5c\x86\xdf\x1a\xf6\xf5\x49\xea\x97\x4e\x0c\x82\xce\x66\xa1\x23\xa4\x0b\xb4\x33\x19\x06\x3e\x7a\xf0\x74\x0d\x2b\x2d\x33\x34\x66\xb5\x42\x1c\x2e\x17\x1f\xb7\xba\xb9\x27\x72\x42\x0f\x14\x68\x28\xe2\xa0\xca\x88\xd0\xf4\xdc\xa1\x2e\x0f\x03\xe8\xe3\x83\x10\xc7\x0a\xa6\xd8\x0a\x0d\x7f\x83\xf4\x74\x35\x4e\x3a\x4b\xd3\xd5\xea\x84\x50\x63\xc1\x3a\xbf\xc5\xdd\xf1\x78\xb4\x86\x6d\xa9\x82\xfc\xa0\x54\xce\x33\xf0\x82\x26\xaf\x8c\x14\xb4\x2e\x59\x84\xff\xc5\xe8\x5a\x25\x5e\x72\xc1\x92\x94\xb3\x5d\xb5\x78\xc8\x05\xbb\x49\x89\x3a\xf4\x17\x69\xc9\x32\x10\x69\x83\x39\x66\x76\x77\xfb\x9b\xdd\x15\xe9\xf5\x8a\x0d\xf6\x57\xe0\xca\xf0\x33\x2c\x2f\xa4\x66\x1b\x32\x5b\x1a\xe6\x90\xbb\x4d\x78\xce\x0b\x6e\xff\xd0\x1e\x4a\x48\x98\xe3\x26\x19\x8f\x1e\x1f\x77\xd0\xb1\xd1\xbc\xe5\x21\x52\x50\xe6\x12\xd8\x50\x77\xca\xb9\xe3\xd9\x59\x2f\x96\x4f\xfc\x84\xdb\x40\x95\x61\x8e\x16\xb7\x9d\x05\x9d\xb8\xde\x0f\x23\xba\x1f\x07\x03\x31\xe5\xc2\xa0\xde\xf8\xf8\x77\x62\x39\x69\xb5\x6f\x65\x19\xec\x8c\x33\x99\xbb\x42\x98\x06\x16\x9e\x69\x33\xb0\xd3\x17\x33\xd4\xbb\xdf\xd7\x95\xd7\xcf\x9b\x33\x3a\xbc\x09\x87\xe5\xcc\x45\x4f\xc2\x13\x71\xa3\xf1\xd5\xf1\xcc\x99\xb9\xfa\x9a\xd7\xbc\x00\x5d\x0e\xc5\xde\x29\x06\xbd\x1d\x7f\x16\x86\xfc\xbb\xe3\x7b\x70\xb6\xd5\xfc\x7b\xb8\x5f\x91\xef\xe9\xfe\x1e\x28\x61\xd0\x5a\x2e\xa6\xbb\x7f\xfb\xb4\xda\xaf\x55\xa1\xfe\xce\xfa\x6f\x54\x23\x42\x4e\xa3\x45\xf4\x3b\x00\x00\xff\xff\x8d\x38\xb9\xa1\x8d\x0c\x00\x00") func yaoApisTableHttpJsonBytes() ([]byte, error) { return bindataRead( @@ -348,7 +348,7 @@ func yaoApisTableHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/table.http.json", size: 3197, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/apis/table.http.json", size: 3213, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -368,7 +368,7 @@ func yaoApisUserHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/user.http.json", size: 1453, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/apis/user.http.json", size: 1453, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -388,7 +388,7 @@ func yaoApisXiangHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/xiang.http.json", size: 1239, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/apis/xiang.http.json", size: 1239, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -408,7 +408,7 @@ func yaoDataDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/data/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -428,7 +428,7 @@ func yaoDataAppJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/app.json", size: 277, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/data/app.json", size: 277, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -448,7 +448,7 @@ func yaoDataIconsDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/data/icons/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -468,7 +468,7 @@ func yaoDataIcons404Png() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -488,7 +488,7 @@ func yaoDataIconsIconIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -508,7 +508,7 @@ func yaoDataIconsIconIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -528,7 +528,7 @@ func yaoDataIconsIconPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -548,7 +548,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -568,7 +568,7 @@ func yaoFlowsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -588,7 +588,7 @@ func yaoFlowsAdminGuardFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/admin/guard.flow.json", size: 559, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/flows/admin/guard.flow.json", size: 559, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -608,7 +608,7 @@ func yaoFlowsMenuFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -628,7 +628,7 @@ func yaoFlowsUserFindFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/user/find.flow.json", size: 328, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/flows/user/find.flow.json", size: 328, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -648,7 +648,7 @@ func yaoLangsEnUsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -668,7 +668,7 @@ func yaoModelsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -688,7 +688,7 @@ func yaoModelsMenuModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/menu.mod.json", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/models/menu.mod.json", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -708,7 +708,7 @@ func yaoModelsUserModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user.mod.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -728,7 +728,7 @@ func yaoModelsWorkflowModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/workflow.mod.json", size: 3333, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/models/workflow.mod.json", size: 3333, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -748,7 +748,7 @@ func yaoTablesReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -768,7 +768,7 @@ func yaoTablesMenuTabJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/menu.tab.json", size: 10471, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/tables/menu.tab.json", size: 10471, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -788,7 +788,7 @@ func yaoTablesUserTabJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/user.tab.json", size: 2357, mode: os.FileMode(420), modTime: time.Unix(1648735794, 0)} + info := bindataFileInfo{name: "yao/tables/user.tab.json", size: 2357, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/table/process.go b/table/process.go index 985d8971..f46ec486 100644 --- a/table/process.go +++ b/table/process.go @@ -75,7 +75,7 @@ func ProcessFind(process *gou.Process) interface{} { // 参数表 process.ValidateArgNums(2) id := process.Args[1] - param := api.MergeDefaultQueryParam(gou.QueryParam{}, 1, process.Sid) + param := api.MergeDefaultQueryParam(gou.QueryParam{}, 2, process.Sid) // 查询数据 response := gou.NewProcess(api.Process, id, param). diff --git a/yao/apis/table.http.json b/yao/apis/table.http.json index a22774ee..31e9570e 100644 --- a/yao/apis/table.http.json +++ b/yao/apis/table.http.json @@ -19,7 +19,7 @@ "path": "/:name/find/:id", "method": "GET", "process": "xiang.table.Find", - "in": ["$param.name", "$param.id"], + "in": ["$param.name", "$param.id", ":query-param"], "out": { "status": 200, "type": "application/json" From 8bd280bb6b639569225c34a28659adcf13cdae75 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 14 Apr 2022 07:47:20 +0800 Subject: [PATCH 32/96] [Add] xiang.table.Find query DSL support --- api/api_test.go | 2 +- flow/flow_test.go | 2 +- table/process.go | 6 +++- tests/apis/demo.http.json | 39 ++++++++++++++++++++ tests/flows/demo/admin.flow.json | 61 ++++++++++++++++++++++++++++++++ tests/flows/demo/token.flow.json | 13 +++++++ 6 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 tests/apis/demo.http.json create mode 100644 tests/flows/demo/admin.flow.json create mode 100644 tests/flows/demo/token.flow.json diff --git a/api/api_test.go b/api/api_test.go index 79f8cf18..8660f461 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -27,6 +27,6 @@ func check(t *testing.T) { wskeys = append(wskeys, key) } - assert.Equal(t, 4, len(keys)) + assert.Equal(t, 5, len(keys)) assert.Equal(t, 1, len(wskeys)) } diff --git a/flow/flow_test.go b/flow/flow_test.go index 394da9b4..b1b06c19 100644 --- a/flow/flow_test.go +++ b/flow/flow_test.go @@ -20,5 +20,5 @@ func check(t *testing.T) { for key := range gou.Flows { keys = append(keys, key) } - assert.Equal(t, 24, len(keys)) + assert.Equal(t, 26, len(keys)) } diff --git a/table/process.go b/table/process.go index f46ec486..063f9860 100644 --- a/table/process.go +++ b/table/process.go @@ -75,7 +75,11 @@ func ProcessFind(process *gou.Process) interface{} { // 参数表 process.ValidateArgNums(2) id := process.Args[1] - param := api.MergeDefaultQueryParam(gou.QueryParam{}, 2, process.Sid) + queryParam := gou.QueryParam{} + if process.NumOfArgs() == 3 { + queryParam = process.ArgsQueryParams(2) + } + param := api.MergeDefaultQueryParam(queryParam, 1, process.Sid) // 查询数据 response := gou.NewProcess(api.Process, id, param). diff --git a/tests/apis/demo.http.json b/tests/apis/demo.http.json new file mode 100644 index 00000000..b04f3239 --- /dev/null +++ b/tests/apis/demo.http.json @@ -0,0 +1,39 @@ +{ + "name": "Demo", + "version": "1.0.0", + "description": "demo", + "group": "demo", + "guard": "-", + "paths": [ + { + "path": "/user", + "method": "GET", + "process": "flows.demo.user", + "in": ["$query.id"], + "out": { + "status": 200, + "type": "application/json" + } + }, + { + "path": "/admin", + "method": "GET", + "process": "flows.demo.admin", + "in": ["$query.id"], + "out": { + "status": 200, + "type": "application/json" + } + }, + { + "path": "/token", + "method": "GET", + "process": "flows.demo.token", + "in": ["$query.id"], + "out": { + "status": 200, + "type": "application/json" + } + } + ] +} diff --git a/tests/flows/demo/admin.flow.json b/tests/flows/demo/admin.flow.json new file mode 100644 index 00000000..9fb64aa6 --- /dev/null +++ b/tests/flows/demo/admin.flow.json @@ -0,0 +1,61 @@ +{ + "label": "Auto Login", + "version": "1.0.0", + "description": "Auto Login for demo", + "nodes": [ + { + "name": "User", + "process": "models.xiang.user.Get", + "args": [ + { + "select": ["id", "email", "name", "type"], + "wheres": [{ "column": "id", "value": 1 }], + "limit": 1 + } + ] + }, + { + "name": "Menu", + "process": "flows.xiang.menu", + "args": ["{{$res.User.0.id}}"] + }, + { + "name": "SID", + "process": "session.start" + }, + { + "name": "JWT", + "process": "xiang.helper.JWTMake", + "args": [ + "{{$res.User.0.id}}", + {}, + { + "timeout": 3600, + "sid": "{{$res.SID}}" + } + ] + }, + { + "name": "Set User", + "process": "session.set", + "args": ["user", "{{$res.User.0}}"] + }, + { + "name": "Set issuer", + "process": "session.set", + "args": ["issuer", "xiang"] + }, + { + "name": "Set User ID", + "process": "session.set", + "args": ["user_id", "{{$res.User.0.id}}"] + } + ], + "output": { + "sid": "{{$res.SID}}", + "user": "{{$res.User.0}}", + "menus": "{{$res.Menu}}", + "token": "{{$res.JWT.token}}", + "expires_at": "{{$res.JWT.expires_at}}" + } +} diff --git a/tests/flows/demo/token.flow.json b/tests/flows/demo/token.flow.json new file mode 100644 index 00000000..ca9002b2 --- /dev/null +++ b/tests/flows/demo/token.flow.json @@ -0,0 +1,13 @@ +{ + "label": "Auto Login", + "version": "1.0.0", + "description": "Auto Login for demo", + "nodes": [ + { + "name": "User", + "process": "flows.demo.admin", + "args": [] + } + ], + "output": "{{$res.User.token}}" +} From af639688df47075a031628622a154d214079e4fc Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 14 Apr 2022 15:54:04 +0800 Subject: [PATCH 33/96] [add] xiang.table.Export process --- Makefile | 2 +- share/types.go | 9 ++- table/process.go | 78 +++++++++++++++++- table/process_test.go | 41 ++++++++++ table/table.go | 125 +++++++++++++++++++++++++++++ tests/tables/hooks/search.tab.json | 2 +- tests/tables/service.json | 2 + 7 files changed, 249 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index b144596a..9c0b5f1b 100644 --- a/Makefile +++ b/Makefile @@ -205,7 +205,7 @@ release: clean # Making artifacts mkdir -p dist - CGO_ENABLED=1 go build -v -o dist/release/yao + CGO_ENABLED=1 CGO_LDFLAGS="-static" go build -v -o dist/release/yao chmod +x dist/release/yao # make clean diff --git a/share/types.go b/share/types.go index 2f6253c9..60ec16a9 100644 --- a/share/types.go +++ b/share/types.go @@ -27,10 +27,11 @@ type ColumnImp Column // Column 字段呈现方式 type Column struct { - Label string `json:"label"` - View Render `json:"view,omitempty"` - Edit Render `json:"edit,omitempty"` - Form Render `json:"form,omitempty"` + Label string `json:"label"` + Export string `json:"export,omitempty"` + View Render `json:"view,omitempty"` + Edit Render `json:"edit,omitempty"` + Form Render `json:"form,omitempty"` Importable } diff --git a/table/process.go b/table/process.go index 063f9860..a6154d5e 100644 --- a/table/process.go +++ b/table/process.go @@ -1,12 +1,19 @@ package table import ( + "crypto/md5" + "encoding/hex" + "fmt" + "os" + "path/filepath" "strings" + "time" "github.com/yaoapp/gou" "github.com/yaoapp/kun/any" "github.com/yaoapp/kun/log" "github.com/yaoapp/kun/maps" + "github.com/yaoapp/yao/xfs" ) func init() { @@ -22,6 +29,7 @@ func init() { gou.RegisterProcessHandler("xiang.table.QuickSave", ProcessQuickSave) gou.RegisterProcessHandler("xiang.table.UpdateIn", ProcessUpdateIn) gou.RegisterProcessHandler("xiang.table.DeleteIn", ProcessDeleteIn) + gou.RegisterProcessHandler("xiang.table.Export", ProcessExport) gou.RegisterProcessHandler("xiang.table.Setting", ProcessSetting) } @@ -36,10 +44,6 @@ func ProcessSearch(process *gou.Process) interface{} { api := table.APIs["search"].ValidateLoop("xiang.table.search") - // if process.NumOfArgsIs(5) && api.IsAllow(process.Args[4]) { - // return nil - // } - // Before Hook process.Args = table.Before(table.Hooks.BeforeSearch, process.Args, process.Sid) @@ -329,3 +333,69 @@ func ProcessSelect(process *gou.Process) interface{} { // After Hook return table.After(table.Hooks.AfterSelect, response, process.Args[1:], process.Sid) } + +// ProcessExport xiang.table.Export (:table, :queryParam, :chunkSize) +// Export query result to Excel +func ProcessExport(process *gou.Process) interface{} { + + process.ValidateArgNums(1) + name := process.ArgsString(0) + table := Select(name) + api := table.APIs["search"].ValidateLoop("xiang.table.search") + + // Make DIR + hash := md5.Sum([]byte(time.Now().Format("20060102-15:04:05"))) + fingerprint := string(hex.EncodeToString(hash[:])) + fingerprint = strings.ToUpper(fingerprint) + dir := time.Now().Format("20060102") + ext := filepath.Ext("export.xlsx") + filename := filepath.Join(dir, fmt.Sprintf("%s%s", fingerprint, ext)) + xfs.Stor.MustMkdirAll(dir, os.ModePerm) + + page := 1 + for page > 0 { + + // Before Hook + process.Args = table.Before(table.Hooks.BeforeSearch, process.Args, process.Sid) + + // 参数表 + process.ValidateArgNums(3) + param := api.MergeDefaultQueryParam(process.ArgsQueryParams(1), 0, process.Sid) + pagesize := process.ArgsInt(2, api.DefaultInt(2)) + if process.NumOfArgs() == 4 { // for search hook + pagesize = process.ArgsInt(3, api.DefaultInt(1)) + } + + // 查询数据 + response := gou.NewProcess(api.Process, param, page, pagesize). + WithGlobal(process.Global). + WithSID(process.Sid). + Run() + + // After Hook + response = table.After(table.Hooks.AfterSearch, response, []interface{}{param, page, pagesize}, process.Sid) + + res, ok := response.(map[string]interface{}) + if !ok { + res, ok = response.(maps.MapStrAny) + if !ok { + page = -1 + continue + } + } + + if _, ok := res["next"]; !ok { + page = -1 + continue + } + + err := table.Export(filename, res["data"], page, pagesize) + if err != nil { + log.Error("Export %s %s", table.Table, err.Error()) + } + + page = any.Of(res["next"]).CInt() + } + + return filename +} diff --git a/table/process_test.go b/table/process_test.go index 7fddf946..c984fbb0 100644 --- a/table/process_test.go +++ b/table/process_test.go @@ -467,3 +467,44 @@ func TestTableProcessSelectWithHook(t *testing.T) { // 清空数据 capsule.Query().Table("service").WhereIn("id", []int{id}).Delete() } + +func TestTableProcessExport(t *testing.T) { + + args := []interface{}{ + "service", + gou.QueryParam{Wheres: []gou.QueryWhere{{Column: "status", Value: "enabled"}}}, + 2, + } + response := gou.NewProcess("xiang.table.Export", args...).Run() + assert.NotNil(t, response) + // fmt.Println(response) + // res := any.Of(response).Map() + // assert.True(t, res.Has("data")) + // assert.True(t, res.Has("next")) + // assert.True(t, res.Has("page")) + // assert.True(t, res.Has("pagecnt")) + // assert.True(t, res.Has("pagesize")) + // assert.True(t, res.Has("prev")) + // assert.True(t, res.Has("total")) + // assert.Equal(t, 1, res.Get("page")) + // assert.Equal(t, 2, res.Get("pagesize")) +} + +func TestTableProcessExportWithHook(t *testing.T) { + + args := []interface{}{"hooks.search"} + response := gou.NewProcess("xiang.table.Export", args...).Run() + + assert.NotNil(t, response) + // res := any.Of(response).Map() + // assert.True(t, res.Has("data")) + // assert.True(t, res.Has("next")) + // assert.True(t, res.Has("page")) + // assert.True(t, res.Has("pagecnt")) + // assert.True(t, res.Has("pagesize")) + // assert.True(t, res.Has("prev")) + // assert.True(t, res.Has("total")) + // assert.Equal(t, 1, res.Get("page")) + // assert.Equal(t, 2, res.Get("pagesize")) + // assert.Equal(t, float64(100), res.Get("after")) +} diff --git a/table/table.go b/table/table.go index 4121c3a5..cc6ee973 100644 --- a/table/table.go +++ b/table/table.go @@ -1,6 +1,7 @@ package table import ( + "errors" "fmt" "io" "os" @@ -8,12 +9,15 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/xuri/excelize/v2" "github.com/yaoapp/gou" "github.com/yaoapp/gou/helper" "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/log" + "github.com/yaoapp/kun/maps" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/share" + "github.com/yaoapp/yao/xfs" ) // Tables 已载入模型 @@ -286,3 +290,124 @@ func (table *Table) loadColumns() { } table.Columns = defaults } + +// Export Export query result to Excel +func (table *Table) Export(filename string, data interface{}, page int, chunkSize int) error { + + rows := []maps.MapStr{} + if values, ok := data.([]maps.MapStrAny); ok { + for _, row := range values { + rows = append(rows, row.Dot()) + } + } else if values, ok := data.([]map[string]interface{}); ok { + for _, row := range values { + rows = append(rows, maps.Of(row).Dot()) + } + } + + columns, err := table.exportSetting() + if err != nil { + return err + } + + if len(columns) == 0 { + return fmt.Errorf("the table does not support export") + } + + filename = filepath.Join(xfs.Stor.Root, filename) + if _, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) { + f := excelize.NewFile() + index := f.GetActiveSheetIndex() + name := f.GetSheetName(index) + f.SetSheetName(name, table.Name) + for i, column := range columns { + axis, err := excelize.CoordinatesToCellName(i+1, 1) + if err != nil { + return err + } + f.SetCellValue(table.Name, axis, column["name"]) + } + if err := f.SaveAs(filename); err != nil { + fmt.Println(err) + return err + } + } + + f, err := excelize.OpenFile(filename) + if err != nil { + return err + } + + defer f.Close() + offset := (page-1)*chunkSize + 2 + for line, row := range rows { + for i, column := range columns { + v := row.Get(column["field"]) + if v != nil { + axis, err := excelize.CoordinatesToCellName(i+1, line+offset) + if err != nil { + return err + } + f.SetCellValue(table.Name, axis, v) + } + } + // fmt.Println("--", line, page, offset, "--") + } + + return f.Save() +} + +func (table *Table) exportSetting() ([]map[string]string, error) { + // Validate params + if table.List.Layout == nil { + return nil, fmt.Errorf("the table layout does not found") + } + + columns, has := table.List.Layout["columns"] + if !has { + return nil, fmt.Errorf("the columns table layout does not found") + } + + fields, ok := columns.([]interface{}) + if !ok { + return nil, fmt.Errorf("Table Layout columns format error") + } + + setting := []map[string]string{} + for _, field := range fields { + f, ok := field.(map[string]interface{}) + if !ok { + continue + } + + n, has := f["name"] + if !has { + continue + } + + name, ok := n.(string) + if !ok { + continue + } + + column, has := table.Columns[name] + if !has { + continue + } + + field := column.Export + if field == "" { + if value, has := column.View.Props["value"]; has { + if valueStr, ok := value.(string); ok { + field = strings.TrimPrefix(valueStr, ":") + } + } + } + + if field != "" && name != "" { + setting = append(setting, map[string]string{"name": name, "field": field}) + } + } + + return setting, nil +} diff --git a/tests/tables/hooks/search.tab.json b/tests/tables/hooks/search.tab.json index d38f27b5..46f6a8b8 100644 --- a/tests/tables/hooks/search.tab.json +++ b/tests/tables/hooks/search.tab.json @@ -22,7 +22,7 @@ "list": { "primary": "id", "layout": { - "columns": [{ "name": "ID", "width": 6 }], + "columns": [{ "name": "id", "width": 6 }, { "name": "城市" }], "filters": [] }, "actions": {} diff --git a/tests/tables/service.json b/tests/tables/service.json index 871bc495..641d9a6b 100644 --- a/tests/tables/service.json +++ b/tests/tables/service.json @@ -316,11 +316,13 @@ "primary": "id", "layout": { "columns": [ + { "name": "id", "width": 6 }, { "name": "服务名称", "width": 6 }, { "name": "所属厂商", "width": 6 }, { "name": "服务类型", "width": 4 }, { "name": "状态", "width": 4 }, { "name": "服务领域", "width": 4 }, + { "name": "行业覆盖", "width": 4 }, { "name": "计费方式", "width": 4 }, { "name": "创建时间", "width": 6 }, { "name": "更新时间", "width": 6 } From 36ecdc1993322ed63653a0b27e693f7fc0d21f6a Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 14 Apr 2022 16:07:24 +0800 Subject: [PATCH 34/96] [add] manual build for Linux release --- Makefile | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Makefile b/Makefile index 9c0b5f1b..49f156c2 100644 --- a/Makefile +++ b/Makefile @@ -203,6 +203,29 @@ release: clean rm -rf .tmp/data rm -rf .tmp/ui +# Making artifacts + mkdir -p dist + CGO_ENABLED=1 go build -v -o dist/release/yao + chmod +x dist/release/yao + +.PHONY: linux-release +linux-release: clean + mkdir -p dist/release + mkdir .tmp + +# Building UI + git clone https://github.com/YaoApp/xgen.git .tmp/ui + sed -ie "s/url('\/icon/url('\/xiang\/icon/g" .tmp/ui/public/icon/md_icon.css + cd .tmp/ui && yarn install && yarn build + +# Packing + mkdir -p .tmp/data + cp -r .tmp/ui/dist .tmp/data/ui + cp -r yao .tmp/data/ + go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/... + rm -rf .tmp/data + rm -rf .tmp/ui + # Making artifacts mkdir -p dist CGO_ENABLED=1 CGO_LDFLAGS="-static" go build -v -o dist/release/yao From 2926ec99f590c430bfe148139a1c72819393f983 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 14 Apr 2022 17:50:29 +0800 Subject: [PATCH 35/96] change CDN url for docker release --- .github/workflows/release-linux.yml | 6 +++--- docker/development/Dockerfile | 2 +- docker/production/Dockerfile | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 85a537ab..850eb00f 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -108,9 +108,9 @@ jobs: run: | coscmd config -a $SECRET_ID -s $SECRET_KEY -b $BUCKET -r $REGION - # - name: Push To Silicon Valley - # run: | - # for file in ./dist/release/*; do coscmd upload $file /archives/; done; + - name: Push To Silicon Valley + run: | + for file in ./dist/release/*; do coscmd upload $file /archives/; done; # - name: Configure COS For Beijing # env: diff --git a/docker/development/Dockerfile b/docker/development/Dockerfile index 71a61313..37498434 100644 --- a/docker/development/Dockerfile +++ b/docker/development/Dockerfile @@ -20,7 +20,7 @@ ARG ARCH RUN groupadd -r yao && useradd -r -g yao yao && \ apt-get update && \ apt-get install -y curl sudo procps net-tools -RUN curl -fsSL "https://release-sv.yaoapps.com/archives/yao-${VERSION}-linux-${ARCH}" > /usr/local/bin/yao && \ +RUN curl -fsSL "https://release-sv-1252011659.cos.na-siliconvalley.myqcloud.com/archives/yao-${VERSION}-linux-${ARCH}" > /usr/local/bin/yao && \ chmod +x /usr/local/bin/yao && \ mkdir -p /data/app diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index 0af6d2c0..bb9def6b 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -18,7 +18,7 @@ FROM alpine:latest ARG VERSION ARG ARCH RUN apk --no-cache add curl -RUN curl -fsSL "https://release-sv.yaoapps.com/archives/yao-${VERSION}-linux-${ARCH}" > /usr/local/bin/yao && \ +RUN curl -fsSL "https://release-sv-1252011659.cos.na-siliconvalley.myqcloud.com/archives/yao-${VERSION}-linux-${ARCH}" > /usr/local/bin/yao && \ chmod +x /usr/local/bin/yao RUN mkdir -p /data/app && \ cd /data/app && /usr/local/bin/yao init && \ From 4d497dd980994dae5ae5e05eb4e4d77fd2f8223e Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 14 Apr 2022 19:14:45 +0800 Subject: [PATCH 36/96] fix xiang.table.export bug --- table/table.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/table/table.go b/table/table.go index cc6ee973..a652892c 100644 --- a/table/table.go +++ b/table/table.go @@ -12,6 +12,7 @@ import ( "github.com/xuri/excelize/v2" "github.com/yaoapp/gou" "github.com/yaoapp/gou/helper" + "github.com/yaoapp/kun/any" "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/log" "github.com/yaoapp/kun/maps" @@ -303,6 +304,10 @@ func (table *Table) Export(filename string, data interface{}, page int, chunkSiz for _, row := range values { rows = append(rows, maps.Of(row).Dot()) } + } else if values, ok := data.([]interface{}); ok { + for _, row := range values { + rows = append(rows, any.Of(row).MapStr().Dot()) + } } columns, err := table.exportSetting() From 1043ba0f0dafb9f40c3aea4df0b9bf37f7e25c79 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 20 Apr 2022 22:04:32 +0800 Subject: [PATCH 37/96] [add] importer output process --- importer/importer.go | 16 ++++++++++++++-- importer/importer_test.go | 2 +- importer/types.go | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/importer/importer.go b/importer/importer.go index 30b594b7..6cdf1fce 100644 --- a/importer/importer.go +++ b/importer/importer.go @@ -382,7 +382,7 @@ func (imp *Importer) SaveAsTemplate(src from.Source) { } // Run 运行导入 -func (imp *Importer) Run(src from.Source, mapping *Mapping) map[string]int { +func (imp *Importer) Run(src from.Source, mapping *Mapping) interface{} { if mapping == nil { mapping = imp.AutoMapping(src) } @@ -424,12 +424,24 @@ func (imp *Importer) Run(src from.Source, mapping *Mapping) map[string]int { log.With(log.F{"line": line, "response": response, "length": length}).Error("导入处理器未返回失败结果") }) - return map[string]int{ + + output := map[string]int{ "total": total, "success": total - failed - ignore, "failure": failed, "ignore": ignore, } + + if imp.Output != "" { + res, err := gou.NewProcess(imp.Output, output).Exec() + if err != nil { + log.With(log.F{"output": imp.Output}).Error(err.Error()) + return output + } + return res + } + + return output } // Start 运行导入(异步) diff --git a/importer/importer_test.go b/importer/importer_test.go index 540aa173..d9d13184 100644 --- a/importer/importer_test.go +++ b/importer/importer_test.go @@ -89,7 +89,7 @@ func TestRunSimple(t *testing.T) { imp := Select("order") mapping := imp.AutoMapping(file) - res := imp.Run(file, mapping) + res := imp.Run(file, mapping).(map[string]int) assert.Equal(t, 1, res["ignore"]) assert.Equal(t, 1, res["failure"]) assert.Equal(t, 2, res["success"]) diff --git a/importer/types.go b/importer/types.go index a574a537..db0ca322 100644 --- a/importer/types.go +++ b/importer/types.go @@ -13,6 +13,7 @@ const PreviewNever = "never" type Importer struct { Title string `json:"title,omitempty"` // 导入名称 Process string `json:"process"` // 处理器名称 + Output string `json:"output,omitempty"` // The process import output Columns []Column `json:"columns"` // 字段列表 Option Option `json:"option,omitempty"` // 导入配置项 Rules map[string]string `json:"rules,omitempty"` // 许可导入规则 From 1e369f7f245d0b39abef603de1d6e1c78c18acda Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 25 Apr 2022 02:37:41 +0800 Subject: [PATCH 38/96] RequestSend: Using system DSN resolver --- network/request.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/network/request.go b/network/request.go index 3e8d23c8..092a7212 100644 --- a/network/request.go +++ b/network/request.go @@ -2,9 +2,11 @@ package network import ( "bytes" + "context" "crypto/tls" "fmt" "io/ioutil" + "net" "net/http" "strings" @@ -94,13 +96,18 @@ func RequestSend(method string, url string, params map[string]interface{}, data } } - // Https - var client *http.Client = &http.Client{} + // Force using system DSN resolver + var dialer = &net.Dialer{Resolver: &net.Resolver{PreferGo: false}} + var dialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialer.DialContext(ctx, network, addr) + } + var client *http.Client = &http.Client{Transport: &http.Transport{DialContext: dialContext}} - // SkipVerify false + // Https SkipVerify false if strings.HasPrefix(url, "https://") { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DialContext: dialContext, } client = &http.Client{Transport: tr} } From 687a477134639e610552904b038ea0cc0f823410 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 26 Apr 2022 10:39:11 +0800 Subject: [PATCH 39/96] [add] RequestSend custom DNS resolve --- go.mod | 2 +- go.sum | 5 +++++ network/request.go | 9 +++------ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index c12b3db3..96636915 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/lib/pq v1.10.4 // indirect github.com/mattn/go-sqlite3 v1.14.9 // indirect - github.com/miekg/dns v1.1.43 // indirect + github.com/miekg/dns v1.1.48 // indirect github.com/mojocn/base64Captcha v1.3.5 github.com/satori/go.uuid v1.2.0 // indirect github.com/spf13/afero v1.6.0 diff --git a/go.sum b/go.sum index e9a42c47..270b0319 100644 --- a/go.sum +++ b/go.sum @@ -352,6 +352,8 @@ github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ= +github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -547,6 +549,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -761,6 +764,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/network/request.go b/network/request.go index 092a7212..6f181346 100644 --- a/network/request.go +++ b/network/request.go @@ -2,15 +2,14 @@ package network import ( "bytes" - "context" "crypto/tls" "fmt" "io/ioutil" - "net" "net/http" "strings" jsoniter "github.com/json-iterator/go" + "github.com/yaoapp/gou/dns" ) // Response 请求响应结果 @@ -97,10 +96,8 @@ func RequestSend(method string, url string, params map[string]interface{}, data } // Force using system DSN resolver - var dialer = &net.Dialer{Resolver: &net.Resolver{PreferGo: false}} - var dialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { - return dialer.DialContext(ctx, network, addr) - } + // var dialer = &net.Dialer{Resolver: &net.Resolver{PreferGo: false}} + var dialContext = dns.DialContext() var client *http.Client = &http.Client{Transport: &http.Transport{DialContext: dialContext}} // Https SkipVerify false From 0089036af2514b808be4dc8bff984305bd5ec939 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 7 May 2022 08:20:48 +0800 Subject: [PATCH 40/96] [add] Table API delete Hooks --- go.mod | 1 - go.sum | 3 --- table/process.go | 8 +++++++- table/table.go | 6 +++++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 96636915..96fa511b 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,6 @@ require ( github.com/json-iterator/go v1.1.12 github.com/lib/pq v1.10.4 // indirect github.com/mattn/go-sqlite3 v1.14.9 // indirect - github.com/miekg/dns v1.1.48 // indirect github.com/mojocn/base64Captcha v1.3.5 github.com/satori/go.uuid v1.2.0 // indirect github.com/spf13/afero v1.6.0 diff --git a/go.sum b/go.sum index 270b0319..855e8cec 100644 --- a/go.sum +++ b/go.sum @@ -350,8 +350,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= -github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ= github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -675,7 +673,6 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/table/process.go b/table/process.go index a6154d5e..279a8a74 100644 --- a/table/process.go +++ b/table/process.go @@ -129,11 +129,17 @@ func ProcessDelete(process *gou.Process) interface{} { table := Select(name) api := table.APIs["delete"].ValidateLoop("xiang.table.delete") + // Before Hook + process.Args = table.Before(table.Hooks.BeforeDelete, process.Args, process.Sid) + id := process.Args[1] - return gou.NewProcess(api.Process, id). + response := gou.NewProcess(api.Process, id). WithGlobal(process.Global). WithSID(process.Sid). Run() + + // After Hook + return table.After(table.Hooks.AfterDelete, response, []interface{}{id}, process.Sid) } // ProcessDeleteWhere xiang.table.DeleteWhere diff --git a/table/table.go b/table/table.go index a652892c..b5c4fce9 100644 --- a/table/table.go +++ b/table/table.go @@ -32,6 +32,8 @@ func Guard(c *gin.Context) { return } + log.Trace("Table Guard FullPath: %s", c.FullPath()) + routes := strings.Split(c.FullPath(), "/") path := routes[len(routes)-1] name, has := c.Params.Get("name") @@ -52,6 +54,8 @@ func Guard(c *gin.Context) { return } + log.Trace("Table Guard: %s %s %s", name, api.Guard, path) + if api.Guard == "-" { c.Next() return @@ -64,7 +68,7 @@ func Guard(c *gin.Context) { guards := strings.Split(api.Guard, ",") for _, guard := range guards { guard = strings.TrimSpace(guard) - log.Trace("Guard Table %s %s", name, guard) + log.Trace("Table Guard: %s %s", name, guard) if guard != "" { if middleware, has := gou.HTTPGuards[guard]; has { middleware(c) From 357d1e7fc871bfe7ae642d828a6d3a68694678f1 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 7 May 2022 11:23:58 +0800 Subject: [PATCH 41/96] Fix Table Guard bug --- table/table.go | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/table/table.go b/table/table.go index b5c4fce9..5cf15a80 100644 --- a/table/table.go +++ b/table/table.go @@ -24,6 +24,21 @@ import ( // Tables 已载入模型 var Tables = map[string]*Table{} +// apiNames mapping +var apiNames = map[string]string{ + "/:name/search": "search", + "/:name/find/:id": "find", + "/:name/save": "save", + "/:name/delete/:id": "delete", + "/:name/insert": "insert", + "/:name/delete/in": "delete-in", + "/:name/delete/where": "delete-where", + "/:name/update/in": "update-in", + "/:name/update/where": "update-where", + "/:name/quicksave": "quicksave", + "/:name/select": "select", +} + // Guard Table guard func Guard(c *gin.Context) { @@ -35,26 +50,42 @@ func Guard(c *gin.Context) { log.Trace("Table Guard FullPath: %s", c.FullPath()) routes := strings.Split(c.FullPath(), "/") - path := routes[len(routes)-1] + if len(routes) < 4 { + log.Trace("Table Guard Routes: %v", routes) + c.Next() + return + } + + path := "/" + strings.Join(routes[4:], "/") + apiName, has := apiNames[path] + if !has { + log.Trace("Table Guard API Name: %v", path) + c.Next() + return + } + name, has := c.Params.Get("name") if !has { + log.Trace("Table Guard Name: %v", c.Params) c.Next() return } table, has := Tables[name] if !has { + log.Trace("Table Guard Table: %s", name) c.Next() return } - api, has := table.APIs[path] + api, has := table.APIs[apiName] if !has { + log.Trace("Table Guard API: %s", apiName) c.Next() return } - log.Trace("Table Guard: %s %s %s", name, api.Guard, path) + log.Trace("Table Guard: %s %s %s %s", name, api.Guard, path, apiName) if api.Guard == "-" { c.Next() From ad0535e17f99a8650b3b7af12f33e54f245e388d Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 25 May 2022 20:33:33 +0800 Subject: [PATCH 42/96] [add] Brain files (demo) --- brain/README.md | 8 ++++++++ brain/behaviors/listen/listen.go | 1 + brain/behaviors/send/send.go | 1 + brain/behaviors/zouyu/README.md | 7 +++++++ brain/behaviors/zouyu/types.go | 3 +++ brain/behaviors/zouyu/zouyu.go | 14 ++++++++++++++ brain/brain.go | 18 ++++++++++++++++++ brain/brain_test.go | 11 +++++++++++ brain/cli/README.md | 1 + brain/cli/cli.go | 1 + brain/cloud/README.md | 3 +++ brain/types.go | 9 +++++++++ brain/web/README.md | 1 + cmd/brain.go | 1 + 14 files changed, 79 insertions(+) create mode 100644 brain/README.md create mode 100644 brain/behaviors/listen/listen.go create mode 100644 brain/behaviors/send/send.go create mode 100644 brain/behaviors/zouyu/README.md create mode 100644 brain/behaviors/zouyu/types.go create mode 100644 brain/behaviors/zouyu/zouyu.go create mode 100644 brain/brain.go create mode 100644 brain/brain_test.go create mode 100644 brain/cli/README.md create mode 100644 brain/cli/cli.go create mode 100644 brain/cloud/README.md create mode 100644 brain/types.go create mode 100644 brain/web/README.md create mode 100644 cmd/brain.go diff --git a/brain/README.md b/brain/README.md new file mode 100644 index 00000000..cc4ac020 --- /dev/null +++ b/brain/README.md @@ -0,0 +1,8 @@ +# Brain + +the robot who helps you build various types of applications + +the robot has two modes : + +1. Generate an App DSL based on the rules +2. Connect AI cloud to generate app DSL based on input ( Not Supported Yet ) diff --git a/brain/behaviors/listen/listen.go b/brain/behaviors/listen/listen.go new file mode 100644 index 00000000..7eb623ac --- /dev/null +++ b/brain/behaviors/listen/listen.go @@ -0,0 +1 @@ +package listen diff --git a/brain/behaviors/send/send.go b/brain/behaviors/send/send.go new file mode 100644 index 00000000..44dc5bad --- /dev/null +++ b/brain/behaviors/send/send.go @@ -0,0 +1 @@ +package send diff --git a/brain/behaviors/zouyu/README.md b/brain/behaviors/zouyu/README.md new file mode 100644 index 00000000..4eebd0f0 --- /dev/null +++ b/brain/behaviors/zouyu/README.md @@ -0,0 +1,7 @@ +# Zouyu + +Zouyu is a benevolent beast in ancient Chinese myths and legends. It is a very precious mythical beast in Lin's country. It is like a tiger, with colorful patterns on its body, and its tail is longer than its body. It looks like a tiger with white hair and black stripes, but it does not eat live animals. Travel thousands of miles a day. + +## Functions + +Connect a Database, generates a dashboard applications diff --git a/brain/behaviors/zouyu/types.go b/brain/behaviors/zouyu/types.go new file mode 100644 index 00000000..cc6aeb76 --- /dev/null +++ b/brain/behaviors/zouyu/types.go @@ -0,0 +1,3 @@ +package zouyu + +type conn struct{} diff --git a/brain/behaviors/zouyu/zouyu.go b/brain/behaviors/zouyu/zouyu.go new file mode 100644 index 00000000..7d3bc864 --- /dev/null +++ b/brain/behaviors/zouyu/zouyu.go @@ -0,0 +1,14 @@ +package zouyu + +// Run the data +func Run(dsn string) {} + +func (conn *conn) models() {} + +func (conn *conn) apis() {} + +func (conn *conn) flows() {} + +func (conn *conn) tables() {} + +func (conn *conn) env() {} diff --git a/brain/brain.go b/brain/brain.go new file mode 100644 index 00000000..02d20a87 --- /dev/null +++ b/brain/brain.go @@ -0,0 +1,18 @@ +package brain + +// NewBehaviors Returns the behaviors based on input words +func NewBehaviors(words string) (*Behaviors, error) { + resp, err := NPL(words) + if err != nil { + return nil, err + } + return resp.Behaviors, nil +} + +// Run the behaviors +func (behaviors *Behaviors) Run() {} + +// NPL the Natural language processing +func NPL(words string) (*Response, error) { + return &Response{Behaviors: &Behaviors{}}, nil +} diff --git a/brain/brain_test.go b/brain/brain_test.go new file mode 100644 index 00000000..480aea9e --- /dev/null +++ b/brain/brain_test.go @@ -0,0 +1,11 @@ +package brain + +import ( + "fmt" + "testing" +) + +func TestNewBehaviors(t *testing.T) { + behaviors, err := NewBehaviors("hello") + fmt.Println(behaviors, err) +} diff --git a/brain/cli/README.md b/brain/cli/README.md new file mode 100644 index 00000000..93c42785 --- /dev/null +++ b/brain/cli/README.md @@ -0,0 +1 @@ +# Command Line Interface diff --git a/brain/cli/cli.go b/brain/cli/cli.go new file mode 100644 index 00000000..7f1e458c --- /dev/null +++ b/brain/cli/cli.go @@ -0,0 +1 @@ +package cli diff --git a/brain/cloud/README.md b/brain/cloud/README.md new file mode 100644 index 00000000..91eec131 --- /dev/null +++ b/brain/cloud/README.md @@ -0,0 +1,3 @@ +# Cloud + +Connecting AI cloud to analyze behaviors diff --git a/brain/types.go b/brain/types.go new file mode 100644 index 00000000..d74eee79 --- /dev/null +++ b/brain/types.go @@ -0,0 +1,9 @@ +package brain + +// Behaviors the Behaviors struct +type Behaviors struct{} + +// Response NPL Response +type Response struct { + Behaviors *Behaviors +} diff --git a/brain/web/README.md b/brain/web/README.md new file mode 100644 index 00000000..42fc93e6 --- /dev/null +++ b/brain/web/README.md @@ -0,0 +1 @@ +# WEB Interface diff --git a/cmd/brain.go b/cmd/brain.go new file mode 100644 index 00000000..1d619dd0 --- /dev/null +++ b/cmd/brain.go @@ -0,0 +1 @@ +package cmd From 6e9bc9b7be536da232ba32bf38dad465bcbe8c86 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 25 May 2022 23:38:53 +0800 Subject: [PATCH 43/96] [add] redis session store --- config/config.go | 249 --------------------------------------------- config/types.go | 11 ++ go.sum | 7 ++ service/service.go | 4 +- share/session.go | 44 ++++---- 5 files changed, 45 insertions(+), 270 deletions(-) diff --git a/config/config.go b/config/config.go index 5857b9e8..65ba54b6 100644 --- a/config/config.go +++ b/config/config.go @@ -124,252 +124,3 @@ func CloseLog() { } } } - -// // Config 系统配置 -// type Config struct { -// XiangConfig -// Service ServiceConfig `json:"service,omitempty"` -// Session SessionConfig `json:"session,omitempty"` -// Database DatabaseConfig `json:"database,omitempty"` -// JWT JWTConfig `json:"jwt,omitempty"` -// Log LogConfig `json:"log,omitempty"` -// } - -// // JWTConfig JWT配置 -// type JWTConfig struct { -// Secret string `json:"secret,omitempty" env:"YAO_JWT_SECRET"` // JWT 密钥 -// } - -// // LogConfig 日志配置 -// type LogConfig struct { -// Access string `json:"access,omitempty" env:"XIANG_LOG_ACCESS" envDefault:"os://stdout"` // 服务访问日志 -// Error string `json:"error,omitempty" env:"XIANG_LOG_ERROR" envDefault:"os://stderr"` // 服务错误日志 -// DB string `json:"database,omitempty" env:"XIANG_LOG_DB" envDefault:"os://stdout"` // 数据库日志 -// Plugin string `json:"plugin,omitempty" env:"XIANG_LOG_PLUGIN" envDefault:"os://stdout"` // 插件日志 -// } - -// // XiangConfig 象传应用引擎配置 -// type XiangConfig struct { -// Mode string `json:"mode,omitempty" env:"XIANG_MODE" envDefault:"release"` // 象传引擎模式 debug/release/test -// Source string `json:"source,omitempty" env:"XIANG_SOURCE" envDefault:"fs://."` // 源码路径(用于单元测试载入数据) -// Path string `json:"path,omitempty" env:"XIANG_PATH" envDefault:"bin://xiang"` // 引擎文件目录 -// Root string `json:"root,omitempty" env:"XIANG_ROOT" envDefault:"fs://."` // 应用文件目录 -// RootUI string `json:"root_ui,omitempty" env:"XIANG_ROOT_UI"` // 应用界面静态文件目录 -// RootDB string `json:"root_db,omitempty" env:"XIANG_ROOT_DB"` // 应用SQLite数据库目录 -// RootData string `json:"root_data,omitempty" env:"XIANG_ROOT_DATA"` // 应用数据文件目录 -// RootAPI string `json:"root_api,omitempty" env:"XIANG_ROOT_API"` // 应用API文件目录 -// RootModel string `json:"root_model,omitempty" env:"XIANG_ROOT_MODEL"` // 应用模型文件目录 -// RootFLow string `json:"root_flow,omitempty" env:"XIANG_ROOT_FLOW"` // 应用业务逻辑文件目录 -// RootPlugin string `json:"root_plugin,omitempty" env:"XIANG_ROOT_PLUGIN"` // 应用业务插件文件目录 -// RootLib string `json:"root_lib,omitempty" env:"XIANG_ROOT_LIB"` // 应用资料库文件目录 -// RootTable string `json:"root_table,omitempty" env:"XIANG_ROOT_TABLE"` // 应用数据表格文件目录 -// RootChart string `json:"root_chart,omitempty" env:"XIANG_ROOT_CHART"` // 应用分析图表文件目录 -// RootPage string `json:"root_page,omitempty" env:"XIANG_ROOT_PAGE"` // 应用通用页面文件目录 -// RootScreen string `json:"root_screen,omitempty" env:"XIANG_ROOT_SCREEN"` // 应用大屏文件目录 -// RootForm string `json:"root_form,omitempty" env:"XIANG_ROOT_FORM"` // 表单文件目录 -// RootWorkFlow string `json:"root_workflow,omitempty" env:"XIANG_ROOT_WORKFLOW"` // 工作流文件目录 -// } - -// // ServiceConfig 服务配置 -// type ServiceConfig struct { -// Debug bool `json:"debug,omitempty" env:"XIANG_SERVICE_DEBUG" envDefault:"false"` // DEBUG 开关 -// HTTPS bool `json:"https,omitempty" env:"XIANG_SERVICE_HTTPS" envDefault:"false"` // HTTPS 开关 -// Cert string `json:"cert,omitempty" env:"XIANG_SERVICE_CERT"` // HTTPS 证书 -// Key string `json:"key,omitempty" env:"XIANG_SERVICE_KEY"` // HTTPS 证书密钥 -// Allow []string `json:"allow,omitempty" env:"XIANG_SERVICE_ALLOW" envSeparator:"|"` // 跨域访问域名列表 -// Host string `json:"host,omitempty" env:"XIANG_SERVICE_HOST" envDefault:"127.0.0.1"` // 服务监听IP -// Port int `json:"port,omitempty" env:"XIANG_SERVICE_PORT" envDefault:"5099"` // 服务监听端口 -// } - -// SessionConfig 会话服务器 -type SessionConfig struct { - Debug bool `json:"debug,omitempty" env:"XIANG_SESSION_DEBUG" envDefault:"false"` // DEBUG 开关 - Hosting bool `json:"hosting,omitempty" env:"XIANG_SESSION_HOSTING" envDefault:"true"` // 会话服务器 - IsCLI bool `json:"iscli,omitempty" env:"XIANG_SESSION_ISCLI" envDefault:"false"` // 是否为客户端启动 - Host string `json:"host,omitempty" env:"XIANG_SESSION_HOST" envDefault:"127.0.0.1"` // 会话服务器IP - Port int `json:"port,omitempty" env:"XIANG_SESSION_PORT" envDefault:"3322"` // 会话服务器端口 -} - -// // DatabaseConfig 数据库配置 -// type DatabaseConfig struct { -// Debug bool `json:"debug,omitempty" env:"XIANG_DB_DEBUG" envDefault:"false"` // DEBUG 开关 -// Driver string `json:"driver,omitempty" env:"XIANG_DB_DRIVER" envDefault:"sqlite3"` // 数据库驱动 ( sqlite3, mysql, postgres) -// Primary []string `json:"primary,omitempty" env:"XIANG_DB_PRIMARY" envSeparator:"|" envDefault:"file:./db/xiang.db"` // 主库连接DSN -// Secondary []string `json:"secondary,omitempty" env:"XIANG_DB_SECONDARY" envSeparator:"|"` // 从库连接DSN -// AESKey string `json:"aeskey,omitempty" env:"XIANG_DB_AESKEY"` // 加密存储KEY -// } - -// // NewConfig 创建配置文件 -// func NewConfig(envfile ...string) Config { - -// filename := os.Getenv("XIANG_ENV_FILE") -// if filename == "" { -// filename = ".env" -// } - -// if len(envfile) > 0 { -// file, err := filepath.Abs(envfile[0]) -// if err == nil { -// // log.Printf("加载环境配置文件%s出错 %s\n", envfile[0], err.Error()) -// // } else { -// filename = file -// } -// } - -// godotenv.Overload(filename) -// // if err != nil { -// // log.Printf("加载环境配置文件%s出错 %s\n", filename, err.Error()) -// // } - -// cfg := Config{} -// if err := env.Parse(&cfg); err != nil { -// exception.New("解析配置文件出错 %s", 500, err.Error()).Throw() -// } - -// cfg.SetDefaults() -// return cfg -// } - -// // NewConfigFrom 创建配置文件 -// func NewConfigFrom(input io.Reader) Config { -// cfg := Config{} -// err := helper.UnmarshalFile(input, &cfg) -// if err != nil { -// exception.New("解析配置文件出错 %s", 500, err.Error()).Throw() -// } -// cfg.SetDefaults() -// return cfg -// } - -// // SetDefaults 设定默认值 -// func (cfg *Config) SetDefaults() { - -// pwd, err := os.Getwd() -// if err != nil { -// log.Fatal(err.Error()) -// } - -// if cfg.Root == "fs://." || cfg.Root == "." { -// cfg.Root = pwd -// } - -// if cfg.RootAPI == "" { -// cfg.RootAPI = cfg.Root + "/apis" -// } -// if cfg.RootFLow == "" { -// cfg.RootFLow = cfg.Root + "/flows" -// } -// if cfg.RootModel == "" { -// cfg.RootModel = cfg.Root + "/models" -// } -// if cfg.RootPlugin == "" { -// cfg.RootPlugin = cfg.Root + "/plugins" -// } - -// if cfg.RootTable == "" { -// cfg.RootTable = cfg.Root + "/tables" -// } - -// if cfg.RootLib == "" { -// cfg.RootLib = cfg.Root + "/libs" -// } - -// if cfg.RootChart == "" { -// cfg.RootChart = cfg.Root + "/charts" -// } - -// if cfg.RootPage == "" { -// cfg.RootPage = cfg.Root + "/pages" -// } - -// if cfg.RootWorkFlow == "" { -// cfg.RootWorkFlow = cfg.Root + "/workflows" -// } - -// if cfg.RootData == "" { -// cfg.RootData = cfg.Root + "/data" -// } - -// if cfg.RootDB == "" { -// cfg.RootDB = cfg.Root + "/db" -// } - -// if cfg.RootUI == "" { -// cfg.RootUI = cfg.Root + "/ui" -// } - -// if len(cfg.Database.Primary) > 0 { -// if strings.HasPrefix(cfg.Database.Primary[0], "file:.") { -// cfg.Database.Primary[0] = strings.ReplaceAll(cfg.Database.Primary[0], "file:.", pwd) -// } else if strings.HasPrefix(cfg.Database.Primary[0], "file:/") { -// cfg.Database.Primary[0] = strings.ReplaceAll(cfg.Database.Primary[0], "file:.", cfg.RootDB) -// } -// } - -// // 过滤数据 -// cfg.RootDB = strings.TrimPrefix(cfg.RootDB, "fs://") -// cfg.RootDB = strings.TrimPrefix(cfg.RootDB, "file://") -// cfg.RootData = strings.TrimPrefix(cfg.RootData, "fs://") -// cfg.RootData = strings.TrimPrefix(cfg.RootData, "file://") -// cfg.RootUI = strings.TrimPrefix(cfg.RootUI, "fs://") -// cfg.RootUI = strings.TrimPrefix(cfg.RootUI, "file://") -// cfg.RootLib = strings.TrimPrefix(cfg.RootLib, "fs://") -// cfg.RootLib = strings.TrimPrefix(cfg.RootLib, "file://") - -// } - -// // SetEnvFile 指定ENV文件 -// func SetEnvFile(filename string) { -// Conf = NewConfig(filename) -// } - -// // SetAppPath 设定应用目录 -// func SetAppPath(root string, envfile ...string) { - -// fullpath, err := filepath.Abs(root) -// if err != nil { -// log.Panic("设定应用目录%s出错 %s\n", root, err.Error()) -// } - -// // 创建应用目录 -// pathinfo, err := os.Stat(fullpath) -// if os.IsNotExist(err) { -// err := os.MkdirAll(fullpath, os.ModePerm) -// if err != nil { -// log.Panic("创建目录失败(%s) %s", root, err) -// } -// } -// pathinfo, err = os.Stat(fullpath) -// if !pathinfo.IsDir() { -// log.Panic("检查应用目录失败(%s) ", err) -// } - -// if !pathinfo.IsDir() { -// log.Panic("应用目录不是文件夹(%s) ", root) -// } - -// // Set ENV -// if len(envfile) > 0 { -// Conf = NewConfig(envfile[0]) -// } - -// // 从加载配置文件 -// Conf.Root = fullpath -// Conf.RootAPI = filepath.Join(fullpath, "/apis") -// Conf.RootFLow = filepath.Join(fullpath, "/flows") -// Conf.RootModel = filepath.Join(fullpath, "/models") -// Conf.RootPlugin = filepath.Join(fullpath, "/plugins") -// Conf.RootTable = filepath.Join(fullpath, "/tables") -// Conf.RootChart = filepath.Join(fullpath, "/charts") -// Conf.RootPage = filepath.Join(fullpath, "/pages") -// Conf.RootWorkFlow = filepath.Join(fullpath, "/workflows") -// Conf.RootData = filepath.Join(fullpath, "/data") -// Conf.RootUI = filepath.Join(fullpath, "/ui") -// Conf.RootDB = filepath.Join(fullpath, "/db") -// Conf.RootLib = filepath.Join(fullpath, "/libs") -// } - -// // IsDebug 是否为调试模式 -// func IsDebug() bool { -// return Conf.Mode == "debug" -// } diff --git a/config/types.go b/config/types.go index 123d7c37..ec183281 100644 --- a/config/types.go +++ b/config/types.go @@ -24,3 +24,14 @@ type DBConfig struct { Secondary []string `json:"secondary,omitempty" env:"YAO_DB_SECONDARY" envSeparator:"|"` // 从库连接DSN AESKey string `json:"aeskey,omitempty" env:"YAO_DB_AESKEY"` // 加密存储KEY } + +// SessionConfig 会话服务器 +type SessionConfig struct { + Store string `json:"store,omitempty" env:"YAO_SESSION_STORE" envDefault:"server"` // The session store. server | redis | file + Host string `json:"host,omitempty" env:"YAO_SESSION_HOST" envDefault:"127.0.0.1"` // The redis host + Port string `json:"port,omitempty" env:"YAO_SESSION_PORT" envDefault:"6379"` // The redis port + Password string `json:"password,omitempty" env:"YAO_SESSION_PASSWORD"` // The redis password + Username string `json:"username,omitempty" env:"YAO_SESSION_USERNAME"` // The redis username + DB string `json:"db,omitempty" env:"YAO_SESSION_DB" envDefault:"1"` // The redis username + IsCLI bool `json:"iscli,omitempty" env:"YAO_SESSION_ISCLI" envDefault:"false"` // Command Line Start +} diff --git a/go.sum b/go.sum index 855e8cec..d5a7b528 100644 --- a/go.sum +++ b/go.sum @@ -80,6 +80,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -102,6 +103,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= @@ -147,6 +149,7 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= @@ -377,15 +380,18 @@ github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4Pfo github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= @@ -895,6 +901,7 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/service/service.go b/service/service.go index 51858b99..aea44d5a 100644 --- a/service/service.go +++ b/service/service.go @@ -12,8 +12,10 @@ var shutdownComplete = make(chan bool) // Start 启动服务 func Start() { - if config.Conf.Session.Hosting && config.Conf.Session.IsCLI == false { + if config.Conf.Session.Store == "server" && config.Conf.Session.IsCLI == false { share.SessionServerStart() + } else if config.Conf.Session.Store == "redis" { + share.SessionConnect() } gou.SetHTTPGuards(Guards) diff --git a/share/session.go b/share/session.go index 68189f9c..8fe5552f 100644 --- a/share/session.go +++ b/share/session.go @@ -7,6 +7,7 @@ import ( "log" klog "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/network" "github.com/buraksezer/olric" @@ -23,31 +24,34 @@ var SessionPort int var SessionMemberPort int func init() { - SessionPort = network.FreePort() - SessionMemberPort = network.FreePort() - klog.Trace("Session port: %d, Member Port: %d", SessionPort, SessionMemberPort) + if config.Conf.Session.Store == "server" { + SessionPort = network.FreePort() + SessionMemberPort = network.FreePort() + klog.Trace("Session port: %d, Member Port: %d", SessionPort, SessionMemberPort) + } } -// SessionConnect 加载会话信息 (废弃->共享方案用 Redis 替代) -// func SessionConnect() { +// SessionConnect Connect redis server +func SessionConnect() { + args := []string{} + if config.Conf.Session.Port == "" { + config.Conf.Session.Port = "6379" + } -// stats, err := sessServer.Stats() -// fmt.Println(stats, err) + if config.Conf.Session.DB == "" { + config.Conf.Session.DB = "1" + } -// var clientConfig = &client.Config{ -// Servers: []string{fmt.Sprintf("%s:%d", "127.0.0.1", SessionPort)}, -// Serializer: serializer.NewMsgpackSerializer(), -// Client: config_olric.NewClient(), -// } + args = append(args, config.Conf.Session.Port, config.Conf.Session.DB, config.Conf.Session.Password) + rdb, err := session.NewRedis(config.Conf.Host, args...) + if err != nil { + panic(err) + } -// c, err := client.New(clientConfig) -// if err != nil { -// exception.New("会话服务器连接失败 %s", 500, err.Error()).Throw() -// } - -// dm := c.NewDMap("local-session") -// session.MemoryUse(session.ClientDMap{DMap: dm}) -// } + session.Register("redis", rdb) + session.Name = "redis" + klog.Trace("Session Store:REDIS HOST:%s PORT:%s DB:%s", config.Conf.Session.Host, config.Conf.Session.Port, config.Conf.Session.DB) +} // SessionServerStop 关闭会话服务器 func SessionServerStop() { From 074b2f42698c8f36f5ae24ece697a880d26fe612 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 26 May 2022 11:19:53 +0800 Subject: [PATCH 44/96] fix session redis store bug --- share/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/session.go b/share/session.go index 8fe5552f..1d5661fd 100644 --- a/share/session.go +++ b/share/session.go @@ -43,7 +43,7 @@ func SessionConnect() { } args = append(args, config.Conf.Session.Port, config.Conf.Session.DB, config.Conf.Session.Password) - rdb, err := session.NewRedis(config.Conf.Host, args...) + rdb, err := session.NewRedis(config.Conf.Session.Host, args...) if err != nil { panic(err) } From 85fdff5ab18416094130e31325a7d7359b44ec0a Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 26 May 2022 21:54:08 +0800 Subject: [PATCH 45/96] [upgrade] golang 1.18 --- .github/workflows/pr-test.yml | 2 +- .github/workflows/release-linux.yml | 2 +- .github/workflows/release-macos.yml | 2 +- .github/workflows/unit-test.yml | 2 +- go.mod | 90 ++++++++++++++++++++++++----- go.sum | 42 +++----------- 6 files changed, 87 insertions(+), 53 deletions(-) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index 759e9115..dfc67826 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [1.17] + go: [1.18] db: [MySQL8.0, MySQL5.7, SQLite3] if: > ${{ github.event.workflow_run.event == 'pull_request' && diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 850eb00f..defc7b14 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -14,7 +14,7 @@ jobs: release: strategy: matrix: - go: [1.17] + go: [1.18] runs-on: "ubuntu-latest" steps: - name: Arm Build diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index ee21abf5..94e3d76c 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -14,7 +14,7 @@ jobs: release: strategy: matrix: - go: [1.17] + go: [1.18] runs-on: "macos-11" steps: - name: Install coscmd diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 6ef61264..9e1e120c 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [1.17] + go: [1.18] db: [MySQL8.0, MySQL5.7, SQLite3] steps: - name: Setup Cache diff --git a/go.mod b/go.mod index 96fa511b..9fc0248b 100644 --- a/go.mod +++ b/go.mod @@ -1,35 +1,20 @@ module github.com/yaoapp/yao -go 1.16 +go 1.18 require ( github.com/aliyun/alibaba-cloud-sdk-go v1.61.1286 github.com/aliyun/aliyun-oss-go-sdk v2.1.10+incompatible - github.com/armon/go-metrics v0.3.10 // indirect - github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect - github.com/buraksezer/consistent v0.9.0 // indirect github.com/buraksezer/olric v0.4.3 github.com/caarlos0/env/v6 v6.9.1 github.com/elazarl/go-bindata-assetfs v1.0.1 github.com/fatih/color v1.13.0 github.com/fsnotify/fsnotify v1.5.1 github.com/gin-gonic/gin v1.7.7 - github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/google/btree v1.0.1 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-msgpack v1.1.5 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/memberlist v0.3.0 // indirect - github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect - github.com/jmoiron/sqlx v1.3.4 // indirect github.com/joho/godotenv v1.3.0 github.com/json-iterator/go v1.1.12 - github.com/lib/pq v1.10.4 // indirect - github.com/mattn/go-sqlite3 v1.14.9 // indirect github.com/mojocn/base64Captcha v1.3.5 - github.com/satori/go.uuid v1.2.0 // indirect github.com/spf13/afero v1.6.0 github.com/spf13/cobra v1.2.1 github.com/stretchr/testify v1.7.0 @@ -38,11 +23,84 @@ require ( github.com/yaoapp/kun v0.9.0 github.com/yaoapp/xun v0.9.0 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab +) + +require ( + github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect + github.com/armon/go-metrics v0.3.10 // indirect + github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/buraksezer/connpool v0.4.0 // indirect + github.com/buraksezer/consistent v0.9.0 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-playground/locales v0.13.0 // indirect + github.com/go-playground/universal-translator v0.17.0 // indirect + github.com/go-playground/validator/v10 v10.4.1 // indirect + github.com/go-redis/redis/v8 v8.11.4 // indirect + github.com/go-sql-driver/mysql v1.6.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/btree v1.0.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-hclog v1.1.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-msgpack v1.1.5 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.4.3 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/logutils v1.0.0 // indirect + github.com/hashicorp/memberlist v0.3.0 // indirect + github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect + github.com/jmoiron/sqlx v1.3.4 // indirect + github.com/leodido/go-urn v1.2.0 // indirect + github.com/lib/pq v1.10.4 // indirect + github.com/mattn/go-colorable v0.1.11 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-sqlite3 v1.14.9 // indirect + github.com/miekg/dns v1.1.48 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/richardlehane/mscfb v1.0.3 // indirect + github.com/richardlehane/msoleps v1.0.1 // indirect + github.com/satori/go.uuid v1.2.0 // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/takama/daemon v1.0.0 // indirect + github.com/ugorji/go/codec v1.1.7 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 // indirect golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect + golang.org/x/mod v0.4.2 // indirect golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef // indirect + golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/tools v0.1.7 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect google.golang.org/grpc v1.42.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/ini.v1 v1.62.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + rogchap.com/v8go v0.7.0 // indirect ) // go env -w GOPRIVATE=github.com/yaoapp/* diff --git a/go.sum b/go.sum index d5a7b528..8ba3c298 100644 --- a/go.sum +++ b/go.sum @@ -99,7 +99,6 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -140,15 +139,12 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= -github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= @@ -317,17 +313,13 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= @@ -397,7 +389,6 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -422,14 +413,9 @@ github.com/richardlehane/mscfb v1.0.3 h1:rD8TBkYWkObWO0oLDFCbwMeZ4KoalxQy+QgniCj github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o= github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= -github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f h1:a7clxaGmmqtdNTXyvrp/lVO/Gnkzlhc/+dLs5v965GM= -github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f/go.mod h1:/mK7FZ3mFYEn9zvNPhpngTyatyehSwte5bJZ4ehL5Xw= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -471,11 +457,8 @@ github.com/takama/daemon v1.0.0 h1:XS3VLnFKmqw2Z7fQ/dHRarrVjdir9G3z7BEP8osjizQ= github.com/takama/daemon v1.0.0/go.mod h1:gKlhcjbqtBODg5v9H1nj5dU1a2j2GemtuWSNLD5rxOE= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= -github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= -github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 h1:EpI0bqf/eX9SdZDwlMmahKM+CDBgNbsXMhsN28XrM8o= @@ -602,7 +585,6 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 h1:DZshvxDdVoeKIbudAdFEKi+f70l51luSy/7b76ibTY0= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -688,13 +670,11 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef h1:fPxZ3Umkct3LZ8gK9nbk+DWDJ9fstZa2grBn+lWVKPs= golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -891,16 +871,12 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ= -gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= -gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From d12c61fe89d0ced78610ba6566029910404c16ce Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 26 May 2022 23:41:38 +0800 Subject: [PATCH 46/96] [test] unit-test workflow --- .github/workflows/pr-test.yml | 8 +++----- .github/workflows/release-linux.yml | 10 +++------- .github/workflows/release-macos.yml | 10 +++------- .github/workflows/unit-test.yml | 8 +++----- Makefile | 7 ++++--- 5 files changed, 16 insertions(+), 27 deletions(-) diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index dfc67826..98a0d6d2 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [1.18] + go: [1.18.2] db: [MySQL8.0, MySQL5.7, SQLite3] if: > ${{ github.event.workflow_run.event == 'pull_request' && @@ -135,15 +135,13 @@ jobs: password: ${{ secrets.UNIT_PASS }} - name: Setup Go ${{ matrix.go }} - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - name: Setup Go Tools run: | - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi + make tools - name: Setup ENV & Host env: diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index defc7b14..6e5639d3 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -14,7 +14,7 @@ jobs: release: strategy: matrix: - go: [1.18] + go: [1.18.2] runs-on: "ubuntu-latest" steps: - name: Arm Build @@ -83,17 +83,13 @@ jobs: uses: actions/checkout@v2 - name: Setup Go ${{ matrix.go }} - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - name: Setup Go Tools run: | - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi - go get github.com/go-bindata/go-bindata/... - go get github.com/elazarl/go-bindata-assetfs/... + make tools - name: Make Artifacts Linux run: | diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 94e3d76c..daa2e376 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -14,7 +14,7 @@ jobs: release: strategy: matrix: - go: [1.18] + go: [1.18.2] runs-on: "macos-11" steps: - name: Install coscmd @@ -75,17 +75,13 @@ jobs: uses: actions/checkout@v2 - name: Setup Go ${{ matrix.go }} - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - name: Setup Go Tools run: | - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi - go get github.com/go-bindata/go-bindata/... - go get github.com/elazarl/go-bindata-assetfs/... + make tools - name: Make Artifacts MacOS run: | diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 9e1e120c..b5f507c7 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [1.18] + go: [1.18.2] db: [MySQL8.0, MySQL5.7, SQLite3] steps: - name: Setup Cache @@ -83,15 +83,13 @@ jobs: password: ${{ secrets.UNIT_PASS }} - name: Setup Go ${{ matrix.go }} - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - name: Setup Go Tools run: | - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi + make tools - name: Setup ENV & Host env: diff --git a/Makefile b/Makefile index 49f156c2..338c84f1 100644 --- a/Makefile +++ b/Makefile @@ -76,9 +76,10 @@ misspell: .PHONY: tools tools: - go install golang.org/x/lint/golint; \ - go install github.com/client9/misspell/cmd/misspell; - + go install golang.org/x/lint/golint@latest; \ + go install github.com/client9/misspell/cmd/misspell@latest; \ + go install github.com/go-bindata/go-bindata/...@latest; + # make plugin .PHONY: plugin plugin: From 0829ae17cd1d23be76b9b30edce7eda40f97bda2 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 27 May 2022 09:28:48 +0800 Subject: [PATCH 47/96] [deps] security update --- go.mod | 20 ++++++++--------- go.sum | 68 +++++++++++++++++++--------------------------------------- 2 files changed, 32 insertions(+), 56 deletions(-) diff --git a/go.mod b/go.mod index 9fc0248b..033e3feb 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/mojocn/base64Captcha v1.3.5 github.com/spf13/afero v1.6.0 github.com/spf13/cobra v1.2.1 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.7.1 github.com/xuri/excelize/v2 v2.5.0 github.com/yaoapp/gou v0.0.0-20211120135538-e5387704eb03 github.com/yaoapp/kun v0.9.0 @@ -48,11 +48,11 @@ require ( github.com/google/btree v1.0.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-hclog v1.1.0 // indirect + github.com/hashicorp/go-hclog v1.2.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v1.1.5 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.4.3 // indirect + github.com/hashicorp/go-plugin v1.4.4 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/logutils v1.0.0 // indirect @@ -63,7 +63,7 @@ require ( github.com/jmoiron/sqlx v1.3.4 // indirect github.com/leodido/go-urn v1.2.0 // indirect github.com/lib/pq v1.10.4 // indirect - github.com/mattn/go-colorable v0.1.11 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-sqlite3 v1.14.9 // indirect github.com/miekg/dns v1.1.48 // indirect @@ -86,20 +86,20 @@ require ( github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 // indirect golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect golang.org/x/mod v0.4.2 // indirect - golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 // indirect + golang.org/x/net v0.0.0-20220526153639-5463443f8c37 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect golang.org/x/tools v0.1.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect - google.golang.org/grpc v1.42.0 // indirect - google.golang.org/protobuf v1.27.1 // indirect + google.golang.org/genproto v0.0.0-20220526192754-51939a95c655 // indirect + google.golang.org/grpc v1.46.2 // indirect + google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.0 // indirect rogchap.com/v8go v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index 8ba3c298..f5ca6ad0 100644 --- a/go.sum +++ b/go.sum @@ -92,9 +92,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -112,11 +111,9 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -238,10 +235,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.1.0 h1:QsGcniKx5/LuX2eYoeL+Np3UKYPNaN7YKpTh29h8rbw= -github.com/hashicorp/go-hclog v1.1.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -251,9 +246,8 @@ github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YN github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= -github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= -github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= +github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= @@ -277,22 +271,17 @@ github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/memberlist v0.3.0 h1:8+567mCcFDnS5ADl7lrpxPMWiFCElyUEeW0gtj34fMA= github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hokaccha/go-prettyjson v0.0.0-20210113012101-fb4e108d2519 h1:nqAlWFEdqI0ClbTDrhDvE/8LeQ4pftrqKUX9w5k0j3s= -github.com/hokaccha/go-prettyjson v0.0.0-20210113012101-fb4e108d2519/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= @@ -321,21 +310,18 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= @@ -349,7 +335,6 @@ github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ= github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= @@ -374,7 +359,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -450,8 +434,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/takama/daemon v1.0.0 h1:XS3VLnFKmqw2Z7fQ/dHRarrVjdir9G3z7BEP8osjizQ= github.com/takama/daemon v1.0.0/go.mod h1:gKlhcjbqtBODg5v9H1nj5dU1a2j2GemtuWSNLD5rxOE= @@ -470,7 +455,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= @@ -538,7 +522,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -583,10 +566,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 h1:DZshvxDdVoeKIbudAdFEKi+f70l51luSy/7b76ibTY0= -golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8= +golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -670,11 +651,9 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef h1:fPxZ3Umkct3LZ8gK9nbk+DWDJ9fstZa2grBn+lWVKPs= -golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -746,7 +725,6 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= @@ -785,7 +763,6 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -827,10 +804,8 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 h1:b9mVrqYfq3P4bCdaLg1qtBnPzUYgglsIdjZkL/fQVOE= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20220526192754-51939a95c655 h1:56rmjc5LUAanErbiNrY+s/Nd47wDQEJkpqS7i43M1I0= +google.golang.org/genproto v0.0.0-20220526192754-51939a95c655/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -851,9 +826,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -866,8 +840,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -889,8 +864,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From eba92b3eba0778df8eccf69201e9ed3d8c05c573 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 27 May 2022 11:28:05 +0800 Subject: [PATCH 48/96] [add] Process yao.crypto.hash --- crypto/crypto.go | 44 +++++++++++++++++++++++++++++++++++++++++++ crypto/crypto_test.go | 32 +++++++++++++++++++++++++++++++ crypto/process.go | 30 +++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 crypto/crypto.go create mode 100644 crypto/crypto_test.go create mode 100644 crypto/process.go diff --git a/crypto/crypto.go b/crypto/crypto.go new file mode 100644 index 00000000..06a5233f --- /dev/null +++ b/crypto/crypto.go @@ -0,0 +1,44 @@ +package crypto + +import ( + "crypto" + "fmt" + + "golang.org/x/crypto/md4" +) + +func init() { + crypto.RegisterHash(crypto.MD4, md4.New) +} + +var hashTypes = map[string]crypto.Hash{ + "MD4": crypto.MD4, + "MD5": crypto.MD5, + "SHA1": crypto.SHA1, + "SHA224": crypto.SHA224, + "SHA256": crypto.SHA256, + "SHA384": crypto.SHA384, + "SHA512": crypto.SHA512, + "MD5SHA1": crypto.MD5SHA1, + "RIPEMD160": crypto.RIPEMD160, + "SHA3_224": crypto.SHA3_224, + "SHA3_256": crypto.SHA3_256, + "SHA3_384": crypto.SHA3_384, + "SHA3_512": crypto.SHA3_512, + "SHA512_224": crypto.SHA512_224, + "SHA512_256": crypto.SHA512_256, + "BLAKE2s_256": crypto.BLAKE2s_256, + "BLAKE2b_256": crypto.BLAKE2b_256, + "BLAKE2b_384": crypto.BLAKE2b_384, + "BLAKE2b_512": crypto.BLAKE2b_512, +} + +// Hash string +func Hash(hash crypto.Hash, value string) (string, error) { + h := hash.New() + _, err := h.Write([]byte(value)) + if err != nil { + return "", err + } + return fmt.Sprintf("%x", h.Sum(nil)), nil +} diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go new file mode 100644 index 00000000..eff57f91 --- /dev/null +++ b/crypto/crypto_test.go @@ -0,0 +1,32 @@ +package crypto + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" +) + +func TestMD4(t *testing.T) { + args := []interface{}{"MD4", "123456"} + res := gou.NewProcess("yao.crypto.hash", args...).Run() + assert.Equal(t, "585028aa0f794af812ee3be8804eb14a", res) +} + +func TestMD5(t *testing.T) { + args := []interface{}{"MD5", "123456"} + res := gou.NewProcess("yao.crypto.hash", args...).Run() + assert.Equal(t, "e10adc3949ba59abbe56e057f20f883e", res) +} + +func TestSHA1(t *testing.T) { + args := []interface{}{"SHA1", "123456"} + res := gou.NewProcess("yao.crypto.hash", args...).Run() + assert.Equal(t, "7c4a8d09ca3762af61e59520943dc26494f8941b", res) +} + +func TestSHA256(t *testing.T) { + args := []interface{}{"SHA256", "123456"} + res := gou.NewProcess("yao.crypto.hash", args...).Run() + assert.Equal(t, "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", res) +} diff --git a/crypto/process.go b/crypto/process.go new file mode 100644 index 00000000..d58f8297 --- /dev/null +++ b/crypto/process.go @@ -0,0 +1,30 @@ +package crypto + +import ( + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" +) + +func init() { + gou.RegisterProcessHandler("yao.crypto.hash", ProcessHash) +} + +// ProcessHash yao.crypto.hash Crypto Hash +// Args[0] string: the hash function name. MD4/MD5/SHA1/SHA224/SHA256/SHA384/SHA512/MD5SHA1/RIPEMD160/SHA3_224/SHA3_256/SHA3_384/SHA3_512/SHA512_224/SHA512_256/BLAKE2s_256/BLAKE2b_256/BLAKE2b_384/BLAKE2b_512 +// Args[1] string: value +func ProcessHash(process *gou.Process) interface{} { + process.ValidateArgNums(1) + typ := process.ArgsString(0) + value := process.ArgsString(1) + + h, has := hashTypes[typ] + if !has { + exception.New("%s does not support", 400, typ).Throw() + } + + res, err := Hash(h, value) + if err != nil { + exception.New("%s error: %s value: %s", 400, typ, err, value).Throw() + } + return res +} From e3cbbb9144b4a694721b31fd473934af4268f6d8 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 27 May 2022 12:34:44 +0800 Subject: [PATCH 49/96] [add] buntdb package --- go.mod | 8 ++++++++ go.sum | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/go.mod b/go.mod index 033e3feb..b6dead85 100644 --- a/go.mod +++ b/go.mod @@ -81,6 +81,14 @@ require ( github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/takama/daemon v1.0.0 // indirect + github.com/tidwall/btree v1.1.0 // indirect + github.com/tidwall/buntdb v1.2.9 // indirect + github.com/tidwall/gjson v1.12.1 // indirect + github.com/tidwall/grect v0.1.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/rtred v0.1.2 // indirect + github.com/tidwall/tinyqueue v0.1.1 // indirect github.com/ugorji/go/codec v1.1.7 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 // indirect diff --git a/go.sum b/go.sum index f5ca6ad0..27d0a5b5 100644 --- a/go.sum +++ b/go.sum @@ -440,6 +440,26 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/takama/daemon v1.0.0 h1:XS3VLnFKmqw2Z7fQ/dHRarrVjdir9G3z7BEP8osjizQ= github.com/takama/daemon v1.0.0/go.mod h1:gKlhcjbqtBODg5v9H1nj5dU1a2j2GemtuWSNLD5rxOE= +github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI= +github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8= +github.com/tidwall/btree v1.1.0 h1:5P+9WU8ui5uhmcg3SoPyTwoI0mVyZ1nps7YQzTZFkYM= +github.com/tidwall/btree v1.1.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4= +github.com/tidwall/buntdb v1.2.9 h1:XVz684P7X6HCTrdr385yDZWB1zt/n20ZNG3M1iGyFm4= +github.com/tidwall/buntdb v1.2.9/go.mod h1:IwyGSvvDg6hnKSIhtdZ0AqhCZGH8ukdtCAzaP8fI1X4= +github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo= +github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg= +github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q= +github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8= +github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8= +github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ= +github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= +github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= From 94a2a6d3c406f0be64ca87028a82195584f96c0b Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 27 May 2022 14:18:59 +0800 Subject: [PATCH 50/96] [add] Buntdb for session store --- cmd/run.go | 3 +- cmd/start.go | 1 - config/types.go | 3 +- engine/user_test.go | 5 -- go.mod | 19 +------ go.sum | 103 ---------------------------------- service/service.go | 13 +++-- share/api_test.go | 2 - share/session.go | 133 ++++++++++++++------------------------------ 9 files changed, 53 insertions(+), 229 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 60e617b0..7e9b88e4 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -20,8 +20,7 @@ var runCmd = &cobra.Command{ Short: L("Execute process"), Long: L("Execute process"), Run: func(cmd *cobra.Command, args []string) { - share.SessionServerStart() // Connect to session server - defer share.SessionServerStop() // Connect to session server + defer share.SessionStop() defer gou.KillPlugins() defer func() { err := exception.Catch(recover()) diff --git a/cmd/start.go b/cmd/start.go index 9395e44c..9d4f1255 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -86,7 +86,6 @@ var startCmd = &cobra.Command{ fmt.Println(color.WhiteString(L("Frontend")), color.GreenString(" http://%s%s/", host, port)) fmt.Println(color.WhiteString(L("Dashboard")), color.GreenString(" http://%s%s/xiang/login/admin", host, port)) fmt.Println(color.WhiteString(L("API")), color.GreenString(" http://%s%s/api", host, port)) - fmt.Println(color.WhiteString(L("SessionPort")), color.GreenString(" %d", share.SessionPort)) fmt.Println(color.WhiteString(L("Listening")), color.GreenString(" %s:%d", config.Conf.Host, config.Conf.Port)) fmt.Println("") diff --git a/config/types.go b/config/types.go index ec183281..ebd3ed22 100644 --- a/config/types.go +++ b/config/types.go @@ -27,7 +27,8 @@ type DBConfig struct { // SessionConfig 会话服务器 type SessionConfig struct { - Store string `json:"store,omitempty" env:"YAO_SESSION_STORE" envDefault:"server"` // The session store. server | redis | file + Store string `json:"store,omitempty" env:"YAO_SESSION_STORE" envDefault:"file"` // The session store. redis | file + File string `json:"file,omitempty" env:"YAO_SESSION_FILE"` // The file path Host string `json:"host,omitempty" env:"YAO_SESSION_HOST" envDefault:"127.0.0.1"` // The redis host Port string `json:"port,omitempty" env:"YAO_SESSION_PORT" envDefault:"6379"` // The redis port Password string `json:"password,omitempty" env:"YAO_SESSION_PASSWORD"` // The redis password diff --git a/engine/user_test.go b/engine/user_test.go index 1d86e97c..03448057 100644 --- a/engine/user_test.go +++ b/engine/user_test.go @@ -4,16 +4,11 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/yaoapp/yao/share" "github.com/yaoapp/yao/user" ) func TestUserAuth(t *testing.T) { - // Start Session Server - share.SessionServerStart() - defer share.SessionServerStop() - res := user.Auth("email", "xiang@iqka.com", "A123456p+") assert.True(t, res.Has("user")) assert.True(t, res.Has("token")) diff --git a/go.mod b/go.mod index b6dead85..d7f1c596 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.18 require ( github.com/aliyun/alibaba-cloud-sdk-go v1.61.1286 github.com/aliyun/aliyun-oss-go-sdk v2.1.10+incompatible - github.com/buraksezer/olric v0.4.3 github.com/caarlos0/env/v6 v6.9.1 github.com/elazarl/go-bindata-assetfs v1.0.1 github.com/fatih/color v1.13.0 @@ -27,12 +26,8 @@ require ( require ( github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect - github.com/armon/go-metrics v0.3.10 // indirect github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/buraksezer/connpool v0.4.0 // indirect - github.com/buraksezer/consistent v0.9.0 // indirect - github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -45,18 +40,10 @@ require ( github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/btree v1.0.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-hclog v1.2.0 // indirect - github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-msgpack v1.1.5 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.4 // indirect - github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/hashicorp/logutils v1.0.0 // indirect - github.com/hashicorp/memberlist v0.3.0 // indirect github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect @@ -72,12 +59,10 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/oklog/run v1.1.0 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/richardlehane/mscfb v1.0.3 // indirect github.com/richardlehane/msoleps v1.0.1 // indirect github.com/satori/go.uuid v1.2.0 // indirect - github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/takama/daemon v1.0.0 // indirect @@ -90,21 +75,19 @@ require ( github.com/tidwall/rtred v0.1.2 // indirect github.com/tidwall/tinyqueue v0.1.1 // indirect github.com/ugorji/go/codec v1.1.7 // indirect - github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 // indirect golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect golang.org/x/mod v0.4.2 // indirect golang.org/x/net v0.0.0-20220526153639-5463443f8c37 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect golang.org/x/tools v0.1.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220526192754-51939a95c655 // indirect google.golang.org/grpc v1.46.2 // indirect google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0 // indirect diff --git a/go.sum b/go.sum index 27d0a5b5..e772ca39 100644 --- a/go.sum +++ b/go.sum @@ -39,15 +39,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1286 h1:+Ah33S/QRnOFp4dM6VV2gYDbFu3Cj4HrRy1VpOhBzm8= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1286/go.mod h1:9CMdKNL3ynIGPpfTcdwTvIm8SGuAZYYC4jFVSSvE1YQ= github.com/aliyun/aliyun-oss-go-sdk v2.1.10+incompatible h1:D3gwOr9qUUmyyBRDbpnATqu+EkqqmigFd3Od6xO1QUU= @@ -55,38 +48,22 @@ github.com/aliyun/aliyun-oss-go-sdk v2.1.10+incompatible/go.mod h1:T/Aws4fEfogEE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= -github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/buraksezer/connpool v0.4.0 h1:fNLvWu0FOtJxL7Sqetm6+040mhZWVs8c6vrke14SEKw= -github.com/buraksezer/connpool v0.4.0/go.mod h1:qPiG7gKXo+EjrwG/yqn2StZM4ek6gcYnnGgFIVKN6b0= -github.com/buraksezer/consistent v0.0.0-20191006190839-693edf70fd72/go.mod h1:OEE5igu/CDjGegM1Jn6ZMo7R6LlV/JChAkjfQQIRLpg= -github.com/buraksezer/consistent v0.9.0 h1:Zfs6bX62wbP3QlbPGKUhqDw7SmNkOzY5bHZIYXYpR5g= -github.com/buraksezer/consistent v0.9.0/go.mod h1:6BrVajWq7wbKZlTOUPs/XVfR8c0maujuPowduSpZqmw= -github.com/buraksezer/olric v0.4.3 h1:A8QKRv/2BiIOWmoCzbsRniW/zbuN80vAJXMfEo4N7qc= -github.com/buraksezer/olric v0.4.3/go.mod h1:xNt+/QiiVuqioJzgTMuiV2LynTNu87L1Tp5289XuU78= github.com/caarlos0/env/v6 v6.9.1 h1:zOkkjM0F6ltnQ5eBX6IPI41UP/KDGEK7rRPwGCNos8k= github.com/caarlos0/env/v6 v6.9.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -130,10 +107,6 @@ github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= @@ -147,10 +120,8 @@ github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Px github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -189,8 +160,6 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -231,31 +200,18 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= -github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -263,13 +219,9 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.3.0 h1:8+567mCcFDnS5ADl7lrpxPMWiFCElyUEeW0gtj34fMA= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= @@ -287,7 +239,6 @@ github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXL github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -296,12 +247,9 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -327,10 +275,7 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ= github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -338,7 +283,6 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -355,7 +299,6 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0= github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -370,29 +313,13 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -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.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/richardlehane/mscfb v1.0.3 h1:rD8TBkYWkObWO0oLDFCbwMeZ4KoalxQy+QgniCj3nKI= github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o= @@ -402,22 +329,16 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -428,7 +349,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -460,12 +380,9 @@ github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8= github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ= github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= -github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 h1:EpI0bqf/eX9SdZDwlMmahKM+CDBgNbsXMhsN28XrM8o= github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= github.com/xuri/excelize/v2 v2.5.0 h1:nDDVfX0qaDuGjAvb+5zTd0Bxxoqa1Ffv9B4kiE23PTM= @@ -489,16 +406,13 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201001193750-eb9a90e9f9cb/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab h1:lnZ4LoV0UMdibeCUfIB2a4uFwRu491WX/VB2reB8xNc= golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -546,7 +460,6 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -555,11 +468,9 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -575,7 +486,6 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -608,30 +518,24 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200930132711-30421366ff76/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -697,7 +601,6 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -705,7 +608,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -714,7 +616,6 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -781,7 +682,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -863,7 +763,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= @@ -874,11 +773,9 @@ gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/service/service.go b/service/service.go index aea44d5a..31044161 100644 --- a/service/service.go +++ b/service/service.go @@ -10,12 +10,11 @@ var shutdown = make(chan bool) var shutdownComplete = make(chan bool) // Start 启动服务 -func Start() { +func Start() error { - if config.Conf.Session.Store == "server" && config.Conf.Session.IsCLI == false { - share.SessionServerStart() - } else if config.Conf.Session.Store == "redis" { - share.SessionConnect() + err := share.SessionStart() + if err != nil { + return err } gou.SetHTTPGuards(Guards) @@ -30,6 +29,8 @@ func Start() { shutdownComplete <- true }, Middlewares...) + + return nil } // StartWithouttSession 启动服务 @@ -61,7 +62,7 @@ func StopWithouttSession(onComplete func()) { func Stop(onComplete func()) { shutdown <- true <-shutdownComplete - share.SessionServerStop() + share.SessionStop() gou.KillPlugins() onComplete() } diff --git a/share/api_test.go b/share/api_test.go index 32f4083b..ed896949 100644 --- a/share/api_test.go +++ b/share/api_test.go @@ -9,8 +9,6 @@ import ( ) func TestGetQueryParam(t *testing.T) { - SessionServerStart() - defer SessionServerStop() sid := session.ID() s := session.Global().ID(sid).Expire(5000 * time.Microsecond) s.MustSet("id", 10086) diff --git a/share/session.go b/share/session.go index 1d5661fd..3534ef0f 100644 --- a/share/session.go +++ b/share/session.go @@ -1,38 +1,36 @@ package share import ( - "context" "fmt" - "io" - "log" + "path/filepath" - klog "github.com/yaoapp/kun/log" + "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" - "github.com/yaoapp/yao/network" - "github.com/buraksezer/olric" - config_olric "github.com/buraksezer/olric/config" "github.com/yaoapp/gou/session" ) -var sessServer *olric.Olric +var sessionDB *session.BuntDB -// SessionPort Session 端口 -var SessionPort int +// SessionStart start session +func SessionStart() error { + if config.Conf.Session.Store == "file" { + return SessionFile() + } else if config.Conf.Session.Store == "redis" { + return SessionRedis() + } + return fmt.Errorf("Session Store config error %s (file|redis)", config.Conf.Session.Store) +} -// SessionMemberPort Session Member Port -var SessionMemberPort int - -func init() { - if config.Conf.Session.Store == "server" { - SessionPort = network.FreePort() - SessionMemberPort = network.FreePort() - klog.Trace("Session port: %d, Member Port: %d", SessionPort, SessionMemberPort) +// SessionStop stop session +func SessionStop() { + if sessionDB != nil { + sessionDB.Close() } } -// SessionConnect Connect redis server -func SessionConnect() { +// SessionRedis Connect redis server +func SessionRedis() error { args := []string{} if config.Conf.Session.Port == "" { config.Conf.Session.Port = "6379" @@ -45,82 +43,35 @@ func SessionConnect() { args = append(args, config.Conf.Session.Port, config.Conf.Session.DB, config.Conf.Session.Password) rdb, err := session.NewRedis(config.Conf.Session.Host, args...) if err != nil { - panic(err) + return err } session.Register("redis", rdb) session.Name = "redis" - klog.Trace("Session Store:REDIS HOST:%s PORT:%s DB:%s", config.Conf.Session.Host, config.Conf.Session.Port, config.Conf.Session.DB) + log.Trace("Session Store:REDIS HOST:%s PORT:%s DB:%s", config.Conf.Session.Host, config.Conf.Session.Port, config.Conf.Session.DB) + return nil } -// SessionServerStop 关闭会话服务器 -func SessionServerStop() { - if sessServer != nil { - sessServer.Shutdown(context.Background()) +// SessionFile Start session file +func SessionFile() error { + file := config.Conf.Session.File + if file == "" { + file = filepath.Join(config.Conf.Root, "data", ".session.db") } -} - -// SessionServerStart 启动会话服务器 -func SessionServerStart() { - - // SessionPort := 0 - c := &config_olric.Config{ - BindAddr: "127.0.0.1", - BindPort: SessionPort, - ReadRepair: false, - ReplicaCount: 1, - WriteQuorum: 1, - ReadQuorum: 1, - MemberCountQuorum: 1, - Peers: []string{}, - DMaps: &config_olric.DMaps{}, - StorageEngines: config_olric.NewStorageEngine(), - Logger: &log.Logger{}, - } - - m, err := config_olric.NewMemberlistConfig("local") - if err != nil { - panic(fmt.Sprintf("unable to create a new memberlist config: %v", err)) - } - m.BindAddr = "127.0.0.1" - m.BindPort = SessionMemberPort - // m.AdvertiseAddr = "127.0.0.1" - // m.AdvertisePort = SessionPort - c.MemberlistConfig = m - - // c.MemberlistConfig.BindAddr = config.Conf.Session.Host - // c.MemberlistConfig.BindPort = 3308 - - // c := config_olric.New("local") - // c.BindAddr = config.Conf.Session.Host - // c.BindPort = config.Conf.Session.Port - - c.Logger.SetOutput(io.Discard) // 暂时关闭日志 - ctx, cancel := context.WithCancel(context.Background()) - c.Started = func() { - defer cancel() - klog.Trace("[INFO] Olric is ready to accept connections") - } - - sessServer, err = olric.New(c) - if err != nil { - klog.Error("Failed to create Olric instance: %v", err) - } - - go func() { - // fmt.Println("Session Port IS:", SessionPort) // DEBUG - err = sessServer.Start() // Call Start at background. It's a blocker call. - if err != nil { - fmt.Println(err) - klog.Panic("olric.Start returned an error: %v", err) - } - }() - - <-ctx.Done() - dm, err := sessServer.NewDMap("local-session") - if err != nil { - klog.Panic("olric.NewDMap returned an error: %v", err) - } - - session.MemoryUse(session.ServerDMap{DMap: dm}) + + file, err := filepath.Abs(file) + if err != nil { + return err + } + + burndb, err := session.NewBuntDB(file) + if err != nil { + return err + } + + session.Register("file", burndb) + session.Name = "file" + sessionDB = burndb + log.Trace("Session Store: File %s", file) + return nil } From 5548bc59dd87103d8f0254c363833233252946ac Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 27 May 2022 19:22:04 +0800 Subject: [PATCH 51/96] [add] Javascript Query object --- query/query.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/query/query.go b/query/query.go index 79f958d5..9039532c 100644 --- a/query/query.go +++ b/query/query.go @@ -2,6 +2,7 @@ package query import ( "github.com/yaoapp/gou" + "github.com/yaoapp/gou/query" dsl "github.com/yaoapp/gou/query/gou" "github.com/yaoapp/kun/log" "github.com/yaoapp/xun/capsule" @@ -10,21 +11,21 @@ import ( // Load 加载查询引擎 func Load(cfg config.Config) { - XiangQuery() + DefaultQuery() } -// XiangQuery 注册应用引擎象 xiang -func XiangQuery() { - gou.RegisterEngine("xiang", &dsl.Query{ +// DefaultQuery register the default engine +func DefaultQuery() { + query.Register("default", &dsl.Query{ Query: capsule.Query(), GetTableName: func(s string) string { if mod, has := gou.Models[s]; has { return mod.MetaData.Table.Name } - // exception.New("%s 数据模型尚未加载", 404).Throw() log.Error("%s model does not load", s) return s }, AESKey: config.Conf.DB.AESKey, }) + query.Alias("default", "xiang") } From 2da06bef6b33708d6b85e4f26d48f5abe90803ce Mon Sep 17 00:00:00 2001 From: QJAutumn <780588304@qq.com> Date: Sat, 28 May 2022 10:16:07 +0800 Subject: [PATCH 52/96] [add] import github.com/yaoapp/yao/crypto --- main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/main.go b/main.go index 98509fdf..6b4514d1 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( _ "github.com/yaoapp/yao/network" _ "github.com/yaoapp/yao/user" _ "github.com/yaoapp/yao/xfs" + _ "github.com/yaoapp/yao/crypto" ) // 主程序 From 873ca3a77432832def7fd42002a5b5371c0f6174 Mon Sep 17 00:00:00 2001 From: QJAutumn <780588304@qq.com> Date: Sat, 28 May 2022 10:37:10 +0800 Subject: [PATCH 53/96] [change] github.com/yaoapp/yao/crypto import order --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 6b4514d1..33f551d4 100644 --- a/main.go +++ b/main.go @@ -2,11 +2,11 @@ package main import ( "github.com/yaoapp/yao/cmd" + _ "github.com/yaoapp/yao/crypto" _ "github.com/yaoapp/yao/helper" _ "github.com/yaoapp/yao/network" _ "github.com/yaoapp/yao/user" _ "github.com/yaoapp/yao/xfs" - _ "github.com/yaoapp/yao/crypto" ) // 主程序 From be4fc68c05e2e6523c2c0c759ba7ccf303151be9 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 9 Jun 2022 14:42:53 +0800 Subject: [PATCH 54/96] [add] DNS Cache & Request CloseIdleConnections --- network/request.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/network/request.go b/network/request.go index 6f181346..41db5a52 100644 --- a/network/request.go +++ b/network/request.go @@ -98,16 +98,18 @@ func RequestSend(method string, url string, params map[string]interface{}, data // Force using system DSN resolver // var dialer = &net.Dialer{Resolver: &net.Resolver{PreferGo: false}} var dialContext = dns.DialContext() - var client *http.Client = &http.Client{Transport: &http.Transport{DialContext: dialContext}} + var tr = &http.Transport{DialContext: dialContext} + var client *http.Client = &http.Client{Transport: tr} // Https SkipVerify false if strings.HasPrefix(url, "https://") { - tr := &http.Transport{ + tr = &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, DialContext: dialContext, } client = &http.Client{Transport: tr} } + defer tr.CloseIdleConnections() resp, err := client.Do(req) if err != nil { From 33ae449cd915bc30ada440f075107f423ce59f6a Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 9 Jun 2022 18:18:40 +0800 Subject: [PATCH 55/96] [add] YAO CMD PRVERSION Support --- Makefile | 33 ++++++++++++++++++++++++++++++++- cmd/version.go | 8 +++++++- share/const.go | 3 +++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 338c84f1..abcea79b 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,11 @@ GO ?= go +GIT ?= git GOFMT ?= gofmt "-s" PACKAGES ?= $(shell $(GO) list ./...) VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/) GOFILES := $(shell find . -name "*.go") VERSION := $(shell grep 'const VERSION =' share/const.go |awk '{print $$4}' |sed 's/\"//g') +COMMIT := $(shell git log | head -n 1 | awk '{print substr($$2, 0, 12)}') # ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) TESTFOLDER := $(shell $(GO) list ./... | grep -vE 'examples|tests*|config') @@ -131,6 +133,9 @@ artifacts-linux: clean rm -rf .tmp/data rm -rf .tmp/ui +# Replace PRVERSION + sed -ie "s/const PRVERSION = \"DEV\"/const PRVERSION = \"${COMMIT}\"/g" share/const.go + # Making artifacts mkdir -p dist CGO_ENABLED=1 CGO_LDFLAGS="-static" GOOS=linux GOARCH=amd64 go build -v -o dist/yao-${VERSION}-linux-amd64 @@ -142,6 +147,10 @@ artifacts-linux: clean ls -l dist/release/ dist/release/yao-${VERSION}-linux-amd64 version +# Reset const +# cp -f share/const.goe share/const.go +# rm -f share/const.goe + # make artifacts-macos .PHONY: artifacts-macos artifacts-macos: clean @@ -159,6 +168,9 @@ artifacts-macos: clean rm -rf .tmp/data rm -rf .tmp/ui +# Replace PRVERSION + sed -ie "s/const PRVERSION = \"DEV\"/const PRVERSION = \"${COMMIT}\"/g" share/const.go + # Making artifacts mkdir -p dist CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -v -o dist/yao-${VERSION}-darwin-amd64 @@ -170,6 +182,10 @@ artifacts-macos: clean ls -l dist/release/ dist/release/yao-${VERSION}-darwin-amd64 version +# Reset const +# cp -f share/const.goe share/const.go +# rm -f share/const.goe + .PHONY: debug debug: clean mkdir -p dist/release @@ -181,11 +197,19 @@ debug: clean go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/... rm -rf .tmp/data + +# Replace PRVERSION + sed -ie "s/const PRVERSION = \"DEV\"/const PRVERSION = \"${COMMIT}-debug\"/g" share/const.go + # Making artifacts mkdir -p dist CGO_ENABLED=1 go build -v -o dist/release/yao-debug chmod +x dist/release/yao-debug +# Reset const + cp -f share/const.goe share/const.go + rm -f share/const.goe + .PHONY: release release: clean mkdir -p dist/release @@ -194,7 +218,7 @@ release: clean # Building UI git clone https://github.com/YaoApp/xgen.git .tmp/ui sed -ie "s/url('\/icon/url('\/xiang\/icon/g" .tmp/ui/public/icon/md_icon.css - cd .tmp/ui && yarn install && yarn build + cd .tmp/ui && cnpm install && npm run build # Packing mkdir -p .tmp/data @@ -204,11 +228,18 @@ release: clean rm -rf .tmp/data rm -rf .tmp/ui +# Replace PRVERSION + sed -ie "s/const PRVERSION = \"DEV\"/const PRVERSION = \"${COMMIT}\"/g" share/const.go + # Making artifacts mkdir -p dist CGO_ENABLED=1 go build -v -o dist/release/yao chmod +x dist/release/yao +# Reset const + cp -f share/const.goe share/const.go + rm share/const.goe + .PHONY: linux-release linux-release: clean mkdir -p dist/release diff --git a/cmd/version.go b/cmd/version.go index 929bb829..5c7b4b26 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -12,7 +12,13 @@ var versionCmd = &cobra.Command{ Short: L("Show version"), Long: L("Show version"), Run: func(cmd *cobra.Command, args []string) { + + version := share.VERSION + if share.PRVERSION != "" { + version = fmt.Sprintf("%s-%s", share.VERSION, share.PRVERSION) + } + // Do Stuff Here - fmt.Println(share.VERSION) + fmt.Println(version) }, } diff --git a/share/const.go b/share/const.go index 3b0cc7fb..55424311 100644 --- a/share/const.go +++ b/share/const.go @@ -3,6 +3,9 @@ package share // VERSION 版本号 const VERSION = "0.9.2" +// PRVERSION PreRelease Version +const PRVERSION = "DEV" + // BUILDIN 打包应用合成一个制品 const BUILDIN = false From c913a62778ae30331b883b47c559783fb47e54ca Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 13 Jun 2022 20:43:36 +0800 Subject: [PATCH 56/96] [add] dump command --- cmd/dump.go | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++++ cmd/root.go | 1 + 2 files changed, 201 insertions(+) create mode 100644 cmd/dump.go diff --git a/cmd/dump.go b/cmd/dump.go new file mode 100644 index 00000000..539c09de --- /dev/null +++ b/cmd/dump.go @@ -0,0 +1,200 @@ +package cmd + +import ( + "archive/zip" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/engine" +) + +var dumpModel string +var dumpCmd = &cobra.Command{ + Use: "dump", + Short: L("Dump the application data"), + Long: L("Dump the application data"), + Run: func(cmd *cobra.Command, args []string) { + defer func() { + err := exception.Catch(recover()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + }() + + Boot() + + path, err := filepath.Abs(".") + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + output := filepath.Join(fmt.Sprintf("%s-%s.zip", filepath.Base(path), time.Now().Format("20060102150405"))) + if len(args) > 0 { + output = args[0] + } + + output, err = filepath.Abs(output) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Load model + err = engine.Load(config.Conf) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + if dumpModel != "" { + mod, has := gou.Models[dumpModel] + if has { + mod.Migrate(true) + } + return + } + + // Export models + files := []string{} + for _, mod := range gou.Models { + + fmt.Printf("\r%s", color.GreenString(L("Export the models: %s (%s)"), mod.Name, mod.MetaData.Table.Name)) + jsonfiles, err := mod.Export(5000, func(curr, total int) { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf("\r%s", color.GreenString(L("Export the models: %s (%s) %d/%d"), mod.Name, mod.MetaData.Table.Name, curr, total)) + }) + + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + files = append(files, jsonfiles...) + } + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf("\r%s\n", color.GreenString(L("Export the models: ✨DONE✨"))) + + // Compress files + err = zipfiles(files, output, func(file string) { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf("\r%s", color.GreenString(L("Compress the files: %s"), file)) + }) + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf("\r%s\n", color.GreenString(L("Compress the files: ✨DONE✨"))) + + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + fmt.Println(color.GreenString("File: %s", output)) + }, +} + +func init() { + dumpCmd.PersistentFlags().StringVarP(&dumpModel, "name", "n", "", L("Model name")) +} + +// gzipfiles +func zipfiles(files []string, output string, process func(file string)) error { + outpath := filepath.Dir(output) + os.MkdirAll(outpath, 0755) + + outfile, err := os.Create(output) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + defer outfile.Close() + + w := zip.NewWriter(outfile) + defer func() { + w.Close() + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + }() + + for _, file := range files { + addFile(w, file, "model", process) + } + + // Add data path + dataPath := filepath.Join(config.Conf.Root, "data") + _, err = os.Stat(dataPath) + if err == nil { + addFolder(w, dataPath, "data", process) + } + + return nil +} + +func addFile(w *zip.Writer, file, baseInZip string, process func(file string)) { + process(filepath.Join(baseInZip, filepath.Base(file))) + dat, err := ioutil.ReadFile(file) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Add some files to the archive. + f, err := w.Create(filepath.Join(baseInZip, filepath.Base(file))) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + _, err = f.Write(dat) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + os.Remove(file) +} + +func addFolder(w *zip.Writer, basePath, baseInZip string, process func(file string)) { + + // Open the Directory + files, err := ioutil.ReadDir(basePath) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + for _, file := range files { + process(filepath.Join(baseInZip, file.Name())) + if !file.IsDir() { + dat, err := ioutil.ReadFile(filepath.Join(basePath, file.Name())) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Add some files to the archive. + f, err := w.Create(filepath.Join(baseInZip, file.Name())) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + _, err = f.Write(dat) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + } else if file.IsDir() { + // Recurse + newBase := filepath.Join(basePath, file.Name()) + addFolder(w, newBase, filepath.Join(baseInZip, file.Name()), process) + } + } +} diff --git a/cmd/root.go b/cmd/root.go index 1106e555..0da3979d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -92,6 +92,7 @@ func init() { runCmd, initCmd, serviceCmd, + dumpCmd, ) // rootCmd.SetHelpCommand(helpCmd) rootCmd.PersistentFlags().StringVarP(&appPath, "app", "a", "", L("Application directory")) From e43bc0b61266746ff292d145b2ef278208b78afd Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 13 Jun 2022 21:32:20 +0800 Subject: [PATCH 57/96] [add] restore command --- cmd/dump.go | 19 +++-- cmd/restore.go | 206 +++++++++++++++++++++++++++++++++++++++++++++++++ cmd/root.go | 1 + 3 files changed, 219 insertions(+), 7 deletions(-) create mode 100644 cmd/restore.go diff --git a/cmd/dump.go b/cmd/dump.go index 539c09de..5c90b900 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -2,6 +2,7 @@ package cmd import ( "archive/zip" + "errors" "fmt" "io/ioutil" "os" @@ -49,6 +50,12 @@ var dumpCmd = &cobra.Command{ os.Exit(1) } + _, err = os.Stat(output) + if !errors.Is(err, os.ErrNotExist) { + fmt.Println(color.RedString("%s exists", output)) + os.Exit(1) + } + // Load model err = engine.Load(config.Conf) if err != nil { @@ -57,10 +64,8 @@ var dumpCmd = &cobra.Command{ } if dumpModel != "" { - mod, has := gou.Models[dumpModel] - if has { - mod.Migrate(true) - } + fmt.Println(color.YellowString(L("Not supported yet"))) + os.Exit(1) return } @@ -100,9 +105,9 @@ var dumpCmd = &cobra.Command{ }, } -func init() { - dumpCmd.PersistentFlags().StringVarP(&dumpModel, "name", "n", "", L("Model name")) -} +// func init() { +// // dumpCmd.PersistentFlags().StringVarP(&dumpModel, "name", "n", "", L("Model name")) +// } // gzipfiles func zipfiles(files []string, output string, process func(file string)) error { diff --git a/cmd/restore.go b/cmd/restore.go new file mode 100644 index 00000000..248e4fc9 --- /dev/null +++ b/cmd/restore.go @@ -0,0 +1,206 @@ +package cmd + +import ( + "archive/zip" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/engine" + "github.com/yaoapp/yao/share" +) + +var restoreForce bool = false +var restoreCmd = &cobra.Command{ + Use: "restore", + Short: L("Restore the application data"), + Long: L("Restore the application data"), + Run: func(cmd *cobra.Command, args []string) { + defer func() { + err := exception.Catch(recover()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + }() + + if len(args) < 1 { + fmt.Println(color.RedString(L("Not enough arguments"))) + fmt.Println(color.WhiteString(share.BUILDNAME + " help")) + os.Exit(1) + } + + zipfile, err := filepath.Abs(args[0]) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + Boot() + + if !restoreForce && config.Conf.Mode == "production" { + fmt.Println(color.WhiteString(L("TRY:")), color.GreenString("%s restore --force", share.BUILDNAME)) + exception.New(L("Retore is not allowed on production mode."), 403).Throw() + } + + // Unzip files + dst := unzipFile(zipfile, func(file string) { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf("\r%s", color.GreenString(L("Unzip the file: %s"), file)) + }) + + // 加载数据模型 + err = engine.Load(config.Conf) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Restore models + restoreModels(filepath.Join(dst, "model")) + + // Restore Data + restoreData(filepath.Join(dst, "data")) + + // Clean + os.RemoveAll(dst) + + fmt.Println(color.GreenString(L("✨DONE✨"))) + }, +} + +func init() { + restoreCmd.PersistentFlags().BoolVarP(&restoreForce, "force", "", false, L("Force restore")) +} + +func restoreData(basePath string) { + + _, err := os.Stat(basePath) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Clean Data + dataPath := filepath.Join(config.Conf.Root, "data") + _, err = os.Stat(dataPath) + if err == nil { + os.RemoveAll(dataPath) + } + + err = os.Rename(basePath, dataPath) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } +} + +func restoreModels(basePath string) { + + files, err := ioutil.ReadDir(basePath) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + // Migrate models + for _, mod := range gou.Models { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf(color.GreenString(L("\rUpdate schema model: %s (%s) "), mod.Name, mod.MetaData.Table.Name)) + err := mod.ForceCreateSchema() + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + } + + fmt.Println("") + + for _, file := range files { + namer := strings.Split(file.Name(), ".") + name := strings.Join(namer[:len(namer)-2], ".") + if mod, has := gou.Models[name]; has { + fmt.Printf("\r%s", strings.Repeat(" ", 80)) + fmt.Printf(color.GreenString(L("\rRestore model: %s (%s) "), mod.Name, mod.MetaData.Table.Name)) + err := mod.Import(filepath.Join(basePath, file.Name())) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + } + } +} + +func unzipFile(file string, process func(file string)) string { + _, err := os.Stat(file) + + if errors.Is(err, os.ErrNotExist) { + fmt.Println(color.RedString("%s not exists", file)) + os.Exit(1) + } + + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + dst := filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s", filepath.Base(file), time.Now().Format("20060102150405"))) + os.MkdirAll(dst, 0755) + + archive, err := zip.OpenReader(file) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + defer archive.Close() + + for _, f := range archive.File { + filePath := filepath.Join(dst, f.Name) + process(f.Name) + + if !strings.HasPrefix(filePath, filepath.Clean(dst)+string(os.PathSeparator)) { + fmt.Println(color.RedString(L("Fatal: invalid file path"))) + os.Exit(1) + } + if f.FileInfo().IsDir() { + os.MkdirAll(filePath, os.ModePerm) + continue + } + + if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + fileInArchive, err := f.Open() + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + if _, err := io.Copy(dstFile, fileInArchive); err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + os.Exit(1) + } + + dstFile.Close() + fileInArchive.Close() + } + + return dst +} diff --git a/cmd/root.go b/cmd/root.go index 0da3979d..5760724a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -93,6 +93,7 @@ func init() { initCmd, serviceCmd, dumpCmd, + restoreCmd, ) // rootCmd.SetHelpCommand(helpCmd) rootCmd.PersistentFlags().StringVarP(&appPath, "app", "a", "", L("Application directory")) From 20ba5ced00a7242e034a819bc9a33944992e3b5f Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 14 Jun 2022 12:52:38 +0800 Subject: [PATCH 58/96] [change] importer process add id and page param --- go.mod | 1 + go.sum | 2 ++ importer/importer.go | 6 +++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d7f1c596..344c8cd8 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,7 @@ require ( github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/go-hclog v1.2.0 // indirect github.com/hashicorp/go-plugin v1.4.4 // indirect diff --git a/go.sum b/go.sum index e772ca39..bcd66b44 100644 --- a/go.sum +++ b/go.sum @@ -190,6 +190,8 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= diff --git a/importer/importer.go b/importer/importer.go index 6cdf1fce..5dca3bf0 100644 --- a/importer/importer.go +++ b/importer/importer.go @@ -7,6 +7,7 @@ import ( "sort" "strings" + "github.com/google/uuid" jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou" "github.com/yaoapp/kun/any" @@ -387,14 +388,17 @@ func (imp *Importer) Run(src from.Source, mapping *Mapping) interface{} { mapping = imp.AutoMapping(src) } + id := uuid.NewString() + page := 0 total := 0 failed := 0 ignore := 0 imp.Chunk(src, mapping, func(line int, data [][]interface{}) { + page++ length := len(data) total = total + length columns, data := imp.DataClean(data, mapping.Columns) - process, err := gou.ProcessOf(imp.Process, columns, data) + process, err := gou.ProcessOf(imp.Process, columns, data, id, page) if err != nil { failed = failed + length log.With(log.F{"line": line}).Error("导入失败: %s", err.Error()) From 4bb2d342cd07f4a34d286fa412bdbcef389e9647 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 15 Jun 2022 18:52:03 +0800 Subject: [PATCH 59/96] [security] update build-in storage API guard --- data/bindata.go | 147 +++++++++++++++++++------------------ yao/apis/storage.http.json | 4 - 2 files changed, 74 insertions(+), 77 deletions(-) diff --git a/data/bindata.go b/data/bindata.go index cefc3054..67dbb4db 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -36,9 +36,9 @@ import ( "bytes" "compress/gzip" "fmt" + "net/http" "io" "io/ioutil" - "net/http" "os" "path/filepath" "strings" @@ -107,6 +107,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } + type assetFile struct { *bytes.Reader name string @@ -208,7 +209,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 22, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 22, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -228,7 +229,7 @@ func yaoDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -248,7 +249,7 @@ func yaoApisReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -268,7 +269,7 @@ func yaoApisChartHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/chart.http.json", size: 624, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/apis/chart.http.json", size: 624, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -288,7 +289,7 @@ func yaoApisImportHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/import.http.json", size: 1676, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/apis/import.http.json", size: 1676, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -308,12 +309,12 @@ func yaoApisPageHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/page.http.json", size: 609, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/apis/page.http.json", size: 609, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoApisStorageHttpJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x91\xcf\x4e\xfa\x40\x10\xc7\xef\x7d\x8a\xc9\xe6\x77\x2c\x6d\x7f\x1e\xb9\x79\x50\x62\x62\x22\x41\x38\x11\x0e\x63\x3b\x94\x62\xd9\x5d\xf7\x8f\x4a\x08\x17\x1f\x40\xaf\xde\x8d\x89\x17\x1f\x0b\x9f\xc3\xec\x52\x8a\x18\x4c\x08\xc1\x78\xd9\xa6\xf3\x99\xfd\xee\x7c\x76\x67\x01\x00\xe3\x38\x21\xd6\x04\xb6\x78\x7f\x5e\x3c\xbc\x7d\x3c\xbe\x2e\x9e\x5e\x58\xe8\xc8\x2d\x29\x5d\x08\xee\xe0\xff\x28\x89\x92\x65\x35\x23\x9d\xaa\x42\x9a\x8a\x7c\xdd\x76\xdc\x3e\x5b\xf6\xe4\x4a\x58\xe9\xe8\x7d\x81\x3c\x8f\xb5\x11\x0a\x73\xaa\x98\x45\x95\x39\x76\x45\xa8\x48\x35\xc6\x77\x66\x09\x24\x9a\x91\x66\x4d\xe8\x07\x00\x00\x33\xbf\x56\x65\xd7\x1e\x5b\x59\x0a\xcc\x7c\xaf\x07\x13\x32\x23\xe1\x93\xda\x17\x97\xdd\x75\xbd\x3e\xa0\xb1\xae\x49\x25\x52\xd2\xba\x1e\x29\x1a\xea\xa8\xf7\x2d\xaf\x70\x3e\x7d\xf6\x6f\x58\x94\x14\xb9\x85\x0d\x6a\x26\xac\x61\xcd\x7a\x26\x00\xa6\x0d\x1a\xeb\xf2\x8e\x92\x24\x5c\x97\xcd\x54\xfa\xcb\x44\x29\xcb\x22\x45\x77\x49\xf1\x58\x0b\xce\xaa\x96\xb9\xff\xce\xc3\x1f\x14\x15\x61\xe6\x4f\xde\x22\xd9\x3a\xd9\xc3\xb1\x43\x98\x9d\x6e\x04\xae\x2c\x6f\x2c\xa9\x69\xe4\x1f\x3f\x84\xd5\x2f\xf1\x54\x64\x7f\xe0\x6d\x55\x79\x30\xe5\x16\x99\x5e\xe7\x7c\x47\x61\x3f\xf7\xfe\xba\xc5\x04\x73\x8a\x25\xcf\x77\xf4\x34\xe2\x9a\xf8\x21\x4d\xbb\x9b\x81\x5b\x5c\x7f\xed\x2d\x03\x80\x41\x30\x0f\x3e\x03\x00\x00\xff\xff\xb6\x5b\xa0\x8d\x44\x04\x00\x00") +var _yaoApisStorageHttpJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x91\xcf\x4e\x3a\x31\x10\xc7\xef\xfb\x14\x93\xe6\x77\xdc\xdf\xee\xea\x91\x9b\x07\x25\x26\x26\x12\x84\x13\xe1\x30\xee\x0e\x4b\x71\x69\x6b\xff\xa8\x84\x70\xf1\x01\xf4\xea\xdd\x98\x78\xf1\xb1\xf0\x39\x4c\xcb\x02\x62\x30\x12\x12\xe3\xa5\x9b\x9d\xcf\x7c\x3b\xfd\xb4\xd3\x08\x80\x09\x1c\x13\x6b\x00\x9b\xbf\x3d\xcd\xef\x5f\xdf\x1f\x5e\xe6\x8f\xcf\x2c\xf6\xe4\x86\xb4\xe1\x52\x78\x78\x90\x64\x49\xb6\xa8\x16\x64\x72\xcd\x95\xad\xc9\xe7\xd8\x51\xeb\x74\xd1\x53\x6a\xe9\x94\xa7\x77\x1c\x45\x99\x1a\x2b\x35\x96\x54\x33\x87\xba\xf0\xec\x92\x50\x93\xfe\x3f\xba\xb5\x0b\xa0\xd0\x0e\x0d\x6b\x40\x2f\x02\x00\x98\x86\xb5\x2e\xfb\xf6\xd4\xa9\x4a\x62\x11\x7a\x03\x18\x93\x1d\xca\xb0\x53\xeb\xfc\xa2\xb3\xae\x2b\x2d\x73\x32\x66\x35\x3e\x19\x98\xa4\xfb\x25\xcb\xfd\xd9\x7b\xec\xdf\x80\x57\x94\xf8\x85\xf5\x57\x4c\x3a\xcb\x1a\xab\xf9\x00\xcc\x58\xb4\xce\xef\x77\x98\x65\xf1\xba\x6c\x27\x2a\x5c\x1c\x2a\x55\xf1\x1c\xfd\x85\xa4\x23\x23\x05\xab\x5b\x66\xe1\x3b\x8b\xbf\xd1\xd1\x84\x45\x98\xbc\x45\xa8\x79\xfc\x83\x4f\x9b\xb0\x38\xd9\x08\x2f\x8d\xae\x1d\xe9\x49\x12\x1e\x35\x86\xe5\x2f\x89\x5c\x16\x7f\xe0\xe8\x74\xb5\x97\x5e\x93\x6c\xb7\x7d\xb6\xa3\x5c\x38\xe3\xfe\x6a\x7c\x8c\x25\xa5\x4a\x94\x3b\x3a\x59\x79\x45\x62\x5f\xab\xce\x66\x78\x8b\xd7\xaf\xbd\x51\x04\xd0\x8f\x66\xd1\x47\x00\x00\x00\xff\xff\x92\xc1\x8a\xc9\xf4\x03\x00\x00") func yaoApisStorageHttpJsonBytes() ([]byte, error) { return bindataRead( @@ -328,7 +329,7 @@ func yaoApisStorageHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/storage.http.json", size: 1092, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/apis/storage.http.json", size: 1012, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -348,7 +349,7 @@ func yaoApisTableHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/table.http.json", size: 3213, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/apis/table.http.json", size: 3213, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -368,7 +369,7 @@ func yaoApisUserHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/user.http.json", size: 1453, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/apis/user.http.json", size: 1453, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -388,7 +389,7 @@ func yaoApisXiangHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/xiang.http.json", size: 1239, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/apis/xiang.http.json", size: 1239, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -408,7 +409,7 @@ func yaoDataDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/data/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -428,7 +429,7 @@ func yaoDataAppJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/app.json", size: 277, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/data/app.json", size: 277, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -448,7 +449,7 @@ func yaoDataIconsDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/data/icons/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -468,7 +469,7 @@ func yaoDataIcons404Png() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -488,7 +489,7 @@ func yaoDataIconsIconIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -508,7 +509,7 @@ func yaoDataIconsIconIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -528,7 +529,7 @@ func yaoDataIconsIconPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -548,7 +549,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -568,7 +569,7 @@ func yaoFlowsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -588,7 +589,7 @@ func yaoFlowsAdminGuardFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/admin/guard.flow.json", size: 559, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/flows/admin/guard.flow.json", size: 559, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -608,7 +609,7 @@ func yaoFlowsMenuFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -628,7 +629,7 @@ func yaoFlowsUserFindFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/user/find.flow.json", size: 328, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/flows/user/find.flow.json", size: 328, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -648,7 +649,7 @@ func yaoLangsEnUsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -668,7 +669,7 @@ func yaoModelsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -688,7 +689,7 @@ func yaoModelsMenuModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/menu.mod.json", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/models/menu.mod.json", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -708,7 +709,7 @@ func yaoModelsUserModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user.mod.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -728,7 +729,7 @@ func yaoModelsWorkflowModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/workflow.mod.json", size: 3333, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/models/workflow.mod.json", size: 3333, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -748,7 +749,7 @@ func yaoTablesReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -768,7 +769,7 @@ func yaoTablesMenuTabJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/menu.tab.json", size: 10471, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/tables/menu.tab.json", size: 10471, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -788,7 +789,7 @@ func yaoTablesUserTabJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/user.tab.json", size: 2357, mode: os.FileMode(420), modTime: time.Unix(1649852606, 0)} + info := bindataFileInfo{name: "yao/tables/user.tab.json", size: 2357, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -918,56 +919,56 @@ type bintree struct { } var _bintree = &bintree{nil, map[string]*bintree{ - "ui": {nil, map[string]*bintree{ - "index.html": {uiIndexHtml, map[string]*bintree{}}, + "ui": &bintree{nil, map[string]*bintree{ + "index.html": &bintree{uiIndexHtml, map[string]*bintree{}}, }}, - "yao": {nil, map[string]*bintree{ - ".DS_Store": {yaoDs_store, map[string]*bintree{}}, - "apis": {nil, map[string]*bintree{ - "README.md": {yaoApisReadmeMd, map[string]*bintree{}}, - "chart.http.json": {yaoApisChartHttpJson, map[string]*bintree{}}, - "import.http.json": {yaoApisImportHttpJson, map[string]*bintree{}}, - "page.http.json": {yaoApisPageHttpJson, map[string]*bintree{}}, - "storage.http.json": {yaoApisStorageHttpJson, map[string]*bintree{}}, - "table.http.json": {yaoApisTableHttpJson, map[string]*bintree{}}, - "user.http.json": {yaoApisUserHttpJson, map[string]*bintree{}}, - "xiang.http.json": {yaoApisXiangHttpJson, map[string]*bintree{}}, + "yao": &bintree{nil, map[string]*bintree{ + ".DS_Store": &bintree{yaoDs_store, map[string]*bintree{}}, + "apis": &bintree{nil, map[string]*bintree{ + "README.md": &bintree{yaoApisReadmeMd, map[string]*bintree{}}, + "chart.http.json": &bintree{yaoApisChartHttpJson, map[string]*bintree{}}, + "import.http.json": &bintree{yaoApisImportHttpJson, map[string]*bintree{}}, + "page.http.json": &bintree{yaoApisPageHttpJson, map[string]*bintree{}}, + "storage.http.json": &bintree{yaoApisStorageHttpJson, map[string]*bintree{}}, + "table.http.json": &bintree{yaoApisTableHttpJson, map[string]*bintree{}}, + "user.http.json": &bintree{yaoApisUserHttpJson, map[string]*bintree{}}, + "xiang.http.json": &bintree{yaoApisXiangHttpJson, map[string]*bintree{}}, }}, - "data": {nil, map[string]*bintree{ - ".DS_Store": {yaoDataDs_store, map[string]*bintree{}}, - "app.json": {yaoDataAppJson, map[string]*bintree{}}, - "icons": {nil, map[string]*bintree{ - ".DS_Store": {yaoDataIconsDs_store, map[string]*bintree{}}, - "404.png": {yaoDataIcons404Png, map[string]*bintree{}}, - "icon.icns": {yaoDataIconsIconIcns, map[string]*bintree{}}, - "icon.ico": {yaoDataIconsIconIco, map[string]*bintree{}}, - "icon.png": {yaoDataIconsIconPng, map[string]*bintree{}}, + "data": &bintree{nil, map[string]*bintree{ + ".DS_Store": &bintree{yaoDataDs_store, map[string]*bintree{}}, + "app.json": &bintree{yaoDataAppJson, map[string]*bintree{}}, + "icons": &bintree{nil, map[string]*bintree{ + ".DS_Store": &bintree{yaoDataIconsDs_store, map[string]*bintree{}}, + "404.png": &bintree{yaoDataIcons404Png, map[string]*bintree{}}, + "icon.icns": &bintree{yaoDataIconsIconIcns, map[string]*bintree{}}, + "icon.ico": &bintree{yaoDataIconsIconIco, map[string]*bintree{}}, + "icon.png": &bintree{yaoDataIconsIconPng, map[string]*bintree{}}, }}, - "index.html": {yaoDataIndexHtml, map[string]*bintree{}}, + "index.html": &bintree{yaoDataIndexHtml, map[string]*bintree{}}, }}, - "flows": {nil, map[string]*bintree{ - "README.md": {yaoFlowsReadmeMd, map[string]*bintree{}}, - "admin": {nil, map[string]*bintree{ - "guard.flow.json": {yaoFlowsAdminGuardFlowJson, map[string]*bintree{}}, + "flows": &bintree{nil, map[string]*bintree{ + "README.md": &bintree{yaoFlowsReadmeMd, map[string]*bintree{}}, + "admin": &bintree{nil, map[string]*bintree{ + "guard.flow.json": &bintree{yaoFlowsAdminGuardFlowJson, map[string]*bintree{}}, }}, - "menu.flow.json": {yaoFlowsMenuFlowJson, map[string]*bintree{}}, - "user": {nil, map[string]*bintree{ - "find.flow.json": {yaoFlowsUserFindFlowJson, map[string]*bintree{}}, + "menu.flow.json": &bintree{yaoFlowsMenuFlowJson, map[string]*bintree{}}, + "user": &bintree{nil, map[string]*bintree{ + "find.flow.json": &bintree{yaoFlowsUserFindFlowJson, map[string]*bintree{}}, }}, }}, - "langs": {nil, map[string]*bintree{ - "en-US.json": {yaoLangsEnUsJson, map[string]*bintree{}}, + "langs": &bintree{nil, map[string]*bintree{ + "en-US.json": &bintree{yaoLangsEnUsJson, map[string]*bintree{}}, }}, - "models": {nil, map[string]*bintree{ - "README.md": {yaoModelsReadmeMd, map[string]*bintree{}}, - "menu.mod.json": {yaoModelsMenuModJson, map[string]*bintree{}}, - "user.mod.json": {yaoModelsUserModJson, map[string]*bintree{}}, - "workflow.mod.json": {yaoModelsWorkflowModJson, map[string]*bintree{}}, + "models": &bintree{nil, map[string]*bintree{ + "README.md": &bintree{yaoModelsReadmeMd, map[string]*bintree{}}, + "menu.mod.json": &bintree{yaoModelsMenuModJson, map[string]*bintree{}}, + "user.mod.json": &bintree{yaoModelsUserModJson, map[string]*bintree{}}, + "workflow.mod.json": &bintree{yaoModelsWorkflowModJson, map[string]*bintree{}}, }}, - "tables": {nil, map[string]*bintree{ - "README.md": {yaoTablesReadmeMd, map[string]*bintree{}}, - "menu.tab.json": {yaoTablesMenuTabJson, map[string]*bintree{}}, - "user.tab.json": {yaoTablesUserTabJson, map[string]*bintree{}}, + "tables": &bintree{nil, map[string]*bintree{ + "README.md": &bintree{yaoTablesReadmeMd, map[string]*bintree{}}, + "menu.tab.json": &bintree{yaoTablesMenuTabJson, map[string]*bintree{}}, + "user.tab.json": &bintree{yaoTablesUserTabJson, map[string]*bintree{}}, }}, }}, }} diff --git a/yao/apis/storage.http.json b/yao/apis/storage.http.json index dbfa49ef..6be9c608 100644 --- a/yao/apis/storage.http.json +++ b/yao/apis/storage.http.json @@ -8,7 +8,6 @@ { "path": "/upload", "method": "POST", - "guard": "-", "process": "xiang.fs.Upload", "in": ["$file.file"], "out": { @@ -19,7 +18,6 @@ { "path": "/readfile", "method": "GET", - "guard": "-", "process": "xiang.fs.ReadFile", "in": ["$query.name", "$query.encode"], "out": { @@ -30,7 +28,6 @@ { "path": "/url", "method": "GET", - "guard": "-", "process": "xiang.fs.GetURL", "in": ["$query.name", "$query.type"], "out": { @@ -41,7 +38,6 @@ { "path": "/token", "method": "GET", - "guard": "-", "process": "xiang.fs.GetToken", "in": ["$query.name"], "out": { From aa4bc897251a2e64b67bdea5a2faa739fe508bd1 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 15 Jun 2022 19:00:11 +0800 Subject: [PATCH 60/96] [change] fmt bindata --- data/bindata.go | 85 ++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/data/bindata.go b/data/bindata.go index 67dbb4db..76b35c75 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -36,9 +36,9 @@ import ( "bytes" "compress/gzip" "fmt" - "net/http" "io" "io/ioutil" + "net/http" "os" "path/filepath" "strings" @@ -107,7 +107,6 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } - type assetFile struct { *bytes.Reader name string @@ -919,56 +918,56 @@ type bintree struct { } var _bintree = &bintree{nil, map[string]*bintree{ - "ui": &bintree{nil, map[string]*bintree{ - "index.html": &bintree{uiIndexHtml, map[string]*bintree{}}, + "ui": {nil, map[string]*bintree{ + "index.html": {uiIndexHtml, map[string]*bintree{}}, }}, - "yao": &bintree{nil, map[string]*bintree{ - ".DS_Store": &bintree{yaoDs_store, map[string]*bintree{}}, - "apis": &bintree{nil, map[string]*bintree{ - "README.md": &bintree{yaoApisReadmeMd, map[string]*bintree{}}, - "chart.http.json": &bintree{yaoApisChartHttpJson, map[string]*bintree{}}, - "import.http.json": &bintree{yaoApisImportHttpJson, map[string]*bintree{}}, - "page.http.json": &bintree{yaoApisPageHttpJson, map[string]*bintree{}}, - "storage.http.json": &bintree{yaoApisStorageHttpJson, map[string]*bintree{}}, - "table.http.json": &bintree{yaoApisTableHttpJson, map[string]*bintree{}}, - "user.http.json": &bintree{yaoApisUserHttpJson, map[string]*bintree{}}, - "xiang.http.json": &bintree{yaoApisXiangHttpJson, map[string]*bintree{}}, + "yao": {nil, map[string]*bintree{ + ".DS_Store": {yaoDs_store, map[string]*bintree{}}, + "apis": {nil, map[string]*bintree{ + "README.md": {yaoApisReadmeMd, map[string]*bintree{}}, + "chart.http.json": {yaoApisChartHttpJson, map[string]*bintree{}}, + "import.http.json": {yaoApisImportHttpJson, map[string]*bintree{}}, + "page.http.json": {yaoApisPageHttpJson, map[string]*bintree{}}, + "storage.http.json": {yaoApisStorageHttpJson, map[string]*bintree{}}, + "table.http.json": {yaoApisTableHttpJson, map[string]*bintree{}}, + "user.http.json": {yaoApisUserHttpJson, map[string]*bintree{}}, + "xiang.http.json": {yaoApisXiangHttpJson, map[string]*bintree{}}, }}, - "data": &bintree{nil, map[string]*bintree{ - ".DS_Store": &bintree{yaoDataDs_store, map[string]*bintree{}}, - "app.json": &bintree{yaoDataAppJson, map[string]*bintree{}}, - "icons": &bintree{nil, map[string]*bintree{ - ".DS_Store": &bintree{yaoDataIconsDs_store, map[string]*bintree{}}, - "404.png": &bintree{yaoDataIcons404Png, map[string]*bintree{}}, - "icon.icns": &bintree{yaoDataIconsIconIcns, map[string]*bintree{}}, - "icon.ico": &bintree{yaoDataIconsIconIco, map[string]*bintree{}}, - "icon.png": &bintree{yaoDataIconsIconPng, map[string]*bintree{}}, + "data": {nil, map[string]*bintree{ + ".DS_Store": {yaoDataDs_store, map[string]*bintree{}}, + "app.json": {yaoDataAppJson, map[string]*bintree{}}, + "icons": {nil, map[string]*bintree{ + ".DS_Store": {yaoDataIconsDs_store, map[string]*bintree{}}, + "404.png": {yaoDataIcons404Png, map[string]*bintree{}}, + "icon.icns": {yaoDataIconsIconIcns, map[string]*bintree{}}, + "icon.ico": {yaoDataIconsIconIco, map[string]*bintree{}}, + "icon.png": {yaoDataIconsIconPng, map[string]*bintree{}}, }}, - "index.html": &bintree{yaoDataIndexHtml, map[string]*bintree{}}, + "index.html": {yaoDataIndexHtml, map[string]*bintree{}}, }}, - "flows": &bintree{nil, map[string]*bintree{ - "README.md": &bintree{yaoFlowsReadmeMd, map[string]*bintree{}}, - "admin": &bintree{nil, map[string]*bintree{ - "guard.flow.json": &bintree{yaoFlowsAdminGuardFlowJson, map[string]*bintree{}}, + "flows": {nil, map[string]*bintree{ + "README.md": {yaoFlowsReadmeMd, map[string]*bintree{}}, + "admin": {nil, map[string]*bintree{ + "guard.flow.json": {yaoFlowsAdminGuardFlowJson, map[string]*bintree{}}, }}, - "menu.flow.json": &bintree{yaoFlowsMenuFlowJson, map[string]*bintree{}}, - "user": &bintree{nil, map[string]*bintree{ - "find.flow.json": &bintree{yaoFlowsUserFindFlowJson, map[string]*bintree{}}, + "menu.flow.json": {yaoFlowsMenuFlowJson, map[string]*bintree{}}, + "user": {nil, map[string]*bintree{ + "find.flow.json": {yaoFlowsUserFindFlowJson, map[string]*bintree{}}, }}, }}, - "langs": &bintree{nil, map[string]*bintree{ - "en-US.json": &bintree{yaoLangsEnUsJson, map[string]*bintree{}}, + "langs": {nil, map[string]*bintree{ + "en-US.json": {yaoLangsEnUsJson, map[string]*bintree{}}, }}, - "models": &bintree{nil, map[string]*bintree{ - "README.md": &bintree{yaoModelsReadmeMd, map[string]*bintree{}}, - "menu.mod.json": &bintree{yaoModelsMenuModJson, map[string]*bintree{}}, - "user.mod.json": &bintree{yaoModelsUserModJson, map[string]*bintree{}}, - "workflow.mod.json": &bintree{yaoModelsWorkflowModJson, map[string]*bintree{}}, + "models": {nil, map[string]*bintree{ + "README.md": {yaoModelsReadmeMd, map[string]*bintree{}}, + "menu.mod.json": {yaoModelsMenuModJson, map[string]*bintree{}}, + "user.mod.json": {yaoModelsUserModJson, map[string]*bintree{}}, + "workflow.mod.json": {yaoModelsWorkflowModJson, map[string]*bintree{}}, }}, - "tables": &bintree{nil, map[string]*bintree{ - "README.md": &bintree{yaoTablesReadmeMd, map[string]*bintree{}}, - "menu.tab.json": &bintree{yaoTablesMenuTabJson, map[string]*bintree{}}, - "user.tab.json": &bintree{yaoTablesUserTabJson, map[string]*bintree{}}, + "tables": {nil, map[string]*bintree{ + "README.md": {yaoTablesReadmeMd, map[string]*bintree{}}, + "menu.tab.json": {yaoTablesMenuTabJson, map[string]*bintree{}}, + "user.tab.json": {yaoTablesUserTabJson, map[string]*bintree{}}, }}, }}, }} From f2b392e8b2832a728c99673ecc8c9e93e19d2dcd Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 16 Jun 2022 23:42:26 +0800 Subject: [PATCH 61/96] v0.10.0 --- share/const.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/const.go b/share/const.go index 55424311..5d7e5b8f 100644 --- a/share/const.go +++ b/share/const.go @@ -1,7 +1,7 @@ package share // VERSION 版本号 -const VERSION = "0.9.2" +const VERSION = "0.10.0" // PRVERSION PreRelease Version const PRVERSION = "DEV" From 886513f9ff3cfeaf31c8cad50eeca54c9ad6d7ee Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 21 Jun 2022 23:45:47 +0800 Subject: [PATCH 62/96] [fix] GetURL ( refactoring later ) --- data/bindata.go | 62 +++++++++++++++++++------------------- xfs/process.go | 13 ++++++++ yao/apis/storage.http.json | 8 ++--- 3 files changed, 47 insertions(+), 36 deletions(-) diff --git a/data/bindata.go b/data/bindata.go index 76b35c75..561e1c11 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -208,7 +208,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 22, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 22, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -228,7 +228,7 @@ func yaoDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -248,7 +248,7 @@ func yaoApisReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -268,7 +268,7 @@ func yaoApisChartHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/chart.http.json", size: 624, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/apis/chart.http.json", size: 624, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -288,7 +288,7 @@ func yaoApisImportHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/import.http.json", size: 1676, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/apis/import.http.json", size: 1676, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -308,12 +308,12 @@ func yaoApisPageHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/page.http.json", size: 609, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/apis/page.http.json", size: 609, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoApisStorageHttpJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x91\xcf\x4e\x3a\x31\x10\xc7\xef\xfb\x14\x93\xe6\x77\xdc\xdf\xee\xea\x91\x9b\x07\x25\x26\x26\x12\x84\x13\xe1\x30\xee\x0e\x4b\x71\x69\x6b\xff\xa8\x84\x70\xf1\x01\xf4\xea\xdd\x98\x78\xf1\xb1\xf0\x39\x4c\xcb\x02\x62\x30\x12\x12\xe3\xa5\x9b\x9d\xcf\x7c\x3b\xfd\xb4\xd3\x08\x80\x09\x1c\x13\x6b\x00\x9b\xbf\x3d\xcd\xef\x5f\xdf\x1f\x5e\xe6\x8f\xcf\x2c\xf6\xe4\x86\xb4\xe1\x52\x78\x78\x90\x64\x49\xb6\xa8\x16\x64\x72\xcd\x95\xad\xc9\xe7\xd8\x51\xeb\x74\xd1\x53\x6a\xe9\x94\xa7\x77\x1c\x45\x99\x1a\x2b\x35\x96\x54\x33\x87\xba\xf0\xec\x92\x50\x93\xfe\x3f\xba\xb5\x0b\xa0\xd0\x0e\x0d\x6b\x40\x2f\x02\x00\x98\x86\xb5\x2e\xfb\xf6\xd4\xa9\x4a\x62\x11\x7a\x03\x18\x93\x1d\xca\xb0\x53\xeb\xfc\xa2\xb3\xae\x2b\x2d\x73\x32\x66\x35\x3e\x19\x98\xa4\xfb\x25\xcb\xfd\xd9\x7b\xec\xdf\x80\x57\x94\xf8\x85\xf5\x57\x4c\x3a\xcb\x1a\xab\xf9\x00\xcc\x58\xb4\xce\xef\x77\x98\x65\xf1\xba\x6c\x27\x2a\x5c\x1c\x2a\x55\xf1\x1c\xfd\x85\xa4\x23\x23\x05\xab\x5b\x66\xe1\x3b\x8b\xbf\xd1\xd1\x84\x45\x98\xbc\x45\xa8\x79\xfc\x83\x4f\x9b\xb0\x38\xd9\x08\x2f\x8d\xae\x1d\xe9\x49\x12\x1e\x35\x86\xe5\x2f\x89\x5c\x16\x7f\xe0\xe8\x74\xb5\x97\x5e\x93\x6c\xb7\x7d\xb6\xa3\x5c\x38\xe3\xfe\x6a\x7c\x8c\x25\xa5\x4a\x94\x3b\x3a\x59\x79\x45\x62\x5f\xab\xce\x66\x78\x8b\xd7\xaf\xbd\x51\x04\xd0\x8f\x66\xd1\x47\x00\x00\x00\xff\xff\x92\xc1\x8a\xc9\xf4\x03\x00\x00") +var _yaoApisStorageHttpJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x92\xbd\x4e\xfb\x30\x10\xc0\xf7\x3c\x85\x65\xfd\xc7\x34\xc9\x9f\xb1\x1b\x03\x54\x48\x48\x54\xa5\x9d\xaa\x0e\x47\x72\x4d\x5d\x52\xdb\xf8\x03\xa8\xaa\x2e\x3c\x00\xac\xec\x08\x89\x85\xc7\x2a\xcf\x81\xec\xa4\xe9\x07\x85\x22\x24\xc4\xe2\xc8\xf7\xbb\xf3\xf9\xe7\xcb\x2c\x20\x84\x72\x98\x20\x6d\x12\xba\x78\x7d\x5c\xdc\xbd\xbc\xdd\x3f\x2f\x1e\x9e\x68\xe8\xc8\x35\x2a\xcd\x04\x77\xf0\x7f\x94\x44\x49\x19\xcd\x50\xa7\x8a\x49\x53\x91\xf5\xb2\xc3\xf6\x49\x99\x93\x2b\x61\xa5\xa3\xb7\x0c\x78\x1e\x6b\x23\x14\xe4\x58\x31\x0b\x2a\x73\xec\x02\x41\xa1\x6a\x8c\x6f\x4c\x09\x24\x98\x91\xa6\x4d\xd2\x0f\x08\x21\x64\xe6\xd7\x2a\xec\xd2\x63\x2b\x0b\x01\x99\xcf\xf5\x60\x82\x66\x24\xfc\x49\xed\xb3\xf3\xee\x2a\x2e\x95\x48\x51\xeb\xba\x7d\x34\xd4\x51\x6f\xab\x96\xb9\xbb\xf7\xe9\xbf\x21\x2b\x30\x72\x0b\x1d\xd4\x4c\x58\x43\x9b\x75\x7f\x42\xa8\x36\x60\xac\x3b\xef\x20\x49\xc2\x55\xd8\x4c\xa5\x7f\x38\x90\xb2\x60\x29\xb8\x07\x89\xc7\x5a\x70\x5a\xa5\xcc\xfd\x77\x1e\x7e\xa2\xa3\x10\x32\xdf\x79\x87\x50\xeb\x68\x8f\x4f\x07\x21\x3b\xde\x28\x5e\x1a\x5d\x59\x54\xd3\xc8\x0f\x35\x24\xcb\x2d\xf2\x54\x64\x7f\xe0\x68\x55\xb1\x57\xaf\xfe\x1f\x1a\x5f\x2b\xb7\xd0\xf4\x3a\xa7\xdf\x14\xf6\xf7\x5e\xdb\x8a\x4b\xe4\x1f\xf5\xb7\xb4\x57\xba\x6c\x02\x39\xc6\x92\xe7\x74\x9f\x61\x79\xf2\x4f\x46\xd8\x42\xd3\xdd\x2c\xde\x61\xf4\x6b\x13\x0b\x08\x19\x04\xf3\xe0\x3d\x00\x00\xff\xff\xeb\x8c\x85\x1f\x02\x04\x00\x00") func yaoApisStorageHttpJsonBytes() ([]byte, error) { return bindataRead( @@ -328,7 +328,7 @@ func yaoApisStorageHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/storage.http.json", size: 1012, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/apis/storage.http.json", size: 1026, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -348,7 +348,7 @@ func yaoApisTableHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/table.http.json", size: 3213, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/apis/table.http.json", size: 3213, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -368,7 +368,7 @@ func yaoApisUserHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/user.http.json", size: 1453, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/apis/user.http.json", size: 1453, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -388,7 +388,7 @@ func yaoApisXiangHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/apis/xiang.http.json", size: 1239, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/apis/xiang.http.json", size: 1239, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -408,7 +408,7 @@ func yaoDataDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/data/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -428,7 +428,7 @@ func yaoDataAppJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/app.json", size: 277, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/data/app.json", size: 277, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -448,7 +448,7 @@ func yaoDataIconsDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/data/icons/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -468,7 +468,7 @@ func yaoDataIcons404Png() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -488,7 +488,7 @@ func yaoDataIconsIconIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -508,7 +508,7 @@ func yaoDataIconsIconIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -528,7 +528,7 @@ func yaoDataIconsIconPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -548,7 +548,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -568,7 +568,7 @@ func yaoFlowsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -588,7 +588,7 @@ func yaoFlowsAdminGuardFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/admin/guard.flow.json", size: 559, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/flows/admin/guard.flow.json", size: 559, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -608,7 +608,7 @@ func yaoFlowsMenuFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -628,7 +628,7 @@ func yaoFlowsUserFindFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/flows/user/find.flow.json", size: 328, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/flows/user/find.flow.json", size: 328, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -648,7 +648,7 @@ func yaoLangsEnUsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -668,7 +668,7 @@ func yaoModelsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -688,7 +688,7 @@ func yaoModelsMenuModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/menu.mod.json", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/models/menu.mod.json", size: 2378, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -708,7 +708,7 @@ func yaoModelsUserModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user.mod.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -728,7 +728,7 @@ func yaoModelsWorkflowModJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/workflow.mod.json", size: 3333, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/models/workflow.mod.json", size: 3333, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -748,7 +748,7 @@ func yaoTablesReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -768,7 +768,7 @@ func yaoTablesMenuTabJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/menu.tab.json", size: 10471, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/tables/menu.tab.json", size: 10471, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -788,7 +788,7 @@ func yaoTablesUserTabJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/tables/user.tab.json", size: 2357, mode: os.FileMode(420), modTime: time.Unix(1655289882, 0)} + info := bindataFileInfo{name: "yao/tables/user.tab.json", size: 2357, mode: os.FileMode(420), modTime: time.Unix(1655826122, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/xfs/process.go b/xfs/process.go index 5a38e7b5..ee8f28e3 100644 --- a/xfs/process.go +++ b/xfs/process.go @@ -11,11 +11,13 @@ import ( "github.com/aliyun/alibaba-cloud-sdk-go/services/sts" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/session" "github.com/yaoapp/kun/any" "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/maps" "github.com/yaoapp/xun" "github.com/yaoapp/yao/data" + "github.com/yaoapp/yao/helper" "github.com/yaoapp/yao/share" ) @@ -143,6 +145,17 @@ func processGetURL(process *gou.Process) interface{} { return string(data.MustAsset("yao/data/icons/404.png")) } + token := process.ArgsString(2) + if token == "" { + exception.New("token is null", 403).Throw() + } + + claims := helper.JwtValidate(token) + userID, err := session.Global().ID(claims.SID).Get("user_id") + if err != nil || userID == "" { + exception.New("session expired", 403).Throw() + } + filename := process.ArgsString(0) body, err := Stor.ReadFile(filename) if err != nil { diff --git a/yao/apis/storage.http.json b/yao/apis/storage.http.json index 6be9c608..04d21b99 100644 --- a/yao/apis/storage.http.json +++ b/yao/apis/storage.http.json @@ -28,12 +28,10 @@ { "path": "/url", "method": "GET", + "guard": "-", "process": "xiang.fs.GetURL", - "in": ["$query.name", "$query.type"], - "out": { - "status": 200, - "type": "image/png" - } + "in": ["$query.name", "$query.type", "$query.token"], + "out": { "status": 200, "type": "image/png" } }, { "path": "/token", From 96fddcd0f94cf562f604709928d3debac9bbe13e Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 21 Jun 2022 23:56:10 +0800 Subject: [PATCH 63/96] [fix] processGetURL session expired --- xfs/process.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xfs/process.go b/xfs/process.go index ee8f28e3..401b67a1 100644 --- a/xfs/process.go +++ b/xfs/process.go @@ -152,7 +152,7 @@ func processGetURL(process *gou.Process) interface{} { claims := helper.JwtValidate(token) userID, err := session.Global().ID(claims.SID).Get("user_id") - if err != nil || userID == "" { + if err != nil || userID == nil { exception.New("session expired", 403).Throw() } From 00b4c8c17ccdadb16bf62d5886eb49ceb0e39c9b Mon Sep 17 00:00:00 2001 From: QJAutumn <780588304@qq.com> Date: Thu, 23 Jun 2022 15:34:00 +0800 Subject: [PATCH 64/96] [add] Import Base64 encryption package --- main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.go b/main.go index 33f551d4..2cf755e5 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,8 @@ package main import ( "github.com/yaoapp/yao/cmd" + + _ "github.com/yaoapp/gou/encoding" _ "github.com/yaoapp/yao/crypto" _ "github.com/yaoapp/yao/helper" _ "github.com/yaoapp/yao/network" From 48fb5b202d8624145db07593db72d8d70d5079ce Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 13 Jul 2022 15:39:44 +0800 Subject: [PATCH 65/96] [add] Chart session data support --- chart/chart.go | 4 +-- chart/chart_test.go | 2 +- chart/process.go | 4 +++ chart/process_test.go | 31 ++++++++++++++++++++-- chart/types.go | 2 +- tests/charts/session.json | 56 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 tests/charts/session.json diff --git a/chart/chart.go b/chart/chart.go index 43da6e2e..4553607b 100644 --- a/chart/chart.go +++ b/chart/chart.go @@ -65,9 +65,7 @@ func LoadFrom(dir string, prefix string) error { // LoadChart 载入数据表格 func LoadChart(source []byte, name string) (*Chart, error) { chart := &Chart{ - Flow: gou.Flow{ - Name: name, - }, + Flow: &gou.Flow{Name: name}, } err := jsoniter.Unmarshal(source, chart) if err != nil { diff --git a/chart/chart_test.go b/chart/chart_test.go index 28f94b0d..6fcd1b15 100644 --- a/chart/chart_test.go +++ b/chart/chart_test.go @@ -25,5 +25,5 @@ func check(t *testing.T) { for key := range Charts { keys = append(keys, key) } - assert.Equal(t, 1, len(keys)) + assert.Equal(t, 2, len(keys)) } diff --git a/chart/process.go b/chart/process.go index f3593edc..d6c73ebd 100644 --- a/chart/process.go +++ b/chart/process.go @@ -33,6 +33,10 @@ func ProcessData(process *gou.Process) interface{} { } } + // with Session + chart.Flow.WithGlobal(process.Global) + chart.Flow.WithSID(process.Sid) + return chart.GetData(params) } diff --git a/chart/process_test.go b/chart/process_test.go index 91067a35..9a7ed568 100644 --- a/chart/process_test.go +++ b/chart/process_test.go @@ -3,12 +3,13 @@ package chart import ( "net/url" "testing" + "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/session" "github.com/yaoapp/kun/any" - "github.com/yaoapp/kun/utils" "github.com/yaoapp/yao/config" _ "github.com/yaoapp/yao/helper" "github.com/yaoapp/yao/model" @@ -68,10 +69,36 @@ func TestProcessData(t *testing.T) { } process := gou.NewProcess("xiang.chart.Data", args...) response := ProcessData(process) - utils.Dump(response) + // utils.Dump(response) assert.NotNil(t, response) res := any.Of(response).Map().Dot() assert.Equal(t, "北京", res.Get("合并.0.城市")) } + +func TestProcessDataGlobalSession(t *testing.T) { + + params := url.Values{ + "from": []string{"1981-01-01", "1990-01-01"}, + } + params.Set("to", "2049-12-31") + + args := []interface{}{ + "session", + params, + &gin.Context{}, + } + + sid := session.ID() + data := time.Now().String() + session.Global().ID(sid).Set("id", data) + process := gou. + NewProcess("xiang.chart.Data", args...). + WithSID(sid). + WithGlobal(map[string]interface{}{"foo": "bar"}) + + response := ProcessData(process) + res := any.Of(response).Map().Dot() + assert.Equal(t, data, res.Get("ID")) +} diff --git a/chart/types.go b/chart/types.go index 446517fb..b7b7e588 100644 --- a/chart/types.go +++ b/chart/types.go @@ -7,7 +7,7 @@ import ( // Chart 图表格式 type Chart struct { - gou.Flow + *gou.Flow APIs map[string]share.API `json:"apis,omitempty"` Filters map[string]share.Filter `json:"filters,omitempty"` Page share.Page `json:"page,omitempty"` diff --git a/tests/charts/session.json b/tests/charts/session.json new file mode 100644 index 00000000..f806316d --- /dev/null +++ b/tests/charts/session.json @@ -0,0 +1,56 @@ +{ + "label": "指标对比", + "version": "1.0.0", + "description": "指标对比用于测试", + "nodes": [{ "name": "ID", "process": "session.Get", "args": ["id"] }], + "output": { + "ID": "{{$res.ID}}" + }, + "apis": { + "data": { + "disable": false, + "guard": "-", + "default": [{ "from": "1980-01-02", "to": "2050-12-31" }] + }, + "setting": { + "disable": false, + "guard": "-" + } + }, + "filters": { + "开始时间": { + "label": "开始时间", + "bind": "from", + "input": { + "type": "date", + "props": { + "placeholder": "请选择开始时间" + } + } + }, + "结束时间": { + "label": "结束时间", + "bind": "to", + "input": { + "type": "date", + "props": { + "placeholder": "请选择结束时间" + } + } + } + }, + "page": { + "primary": "城市", + "layout": { + "filters": [ + { "name": "开始时间", "width": 6 }, + { "name": "结束时间", "width": 6 } + ], + "charts": [ + { "type": "line", "props": {} }, + { "type": "bar", "props": {} } + ] + }, + "actions": {} + } +} From 382f6aa7d318517a3e83ec2787c7c5d2652fe8aa Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 13 Jul 2022 16:01:29 +0800 Subject: [PATCH 66/96] [add] yao.crypto.hmac process --- crypto/crypto.go | 11 +++++++++++ crypto/crypto_test.go | 24 ++++++++++++++++++++++++ crypto/process.go | 23 +++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/crypto/crypto.go b/crypto/crypto.go index 06a5233f..a0dddcda 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -2,6 +2,7 @@ package crypto import ( "crypto" + "crypto/hmac" "fmt" "golang.org/x/crypto/md4" @@ -42,3 +43,13 @@ func Hash(hash crypto.Hash, value string) (string, error) { } return fmt.Sprintf("%x", h.Sum(nil)), nil } + +// Hmac the Keyed-Hash Message Authentication Code (HMAC) +func Hmac(hash crypto.Hash, value string, key string) (string, error) { + mac := hmac.New(hash.New, []byte(key)) + _, err := mac.Write([]byte(value)) + if err != nil { + return "", err + } + return fmt.Sprintf("%x", mac.Sum(nil)), nil +} diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index eff57f91..a1552942 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -8,25 +8,49 @@ import ( ) func TestMD4(t *testing.T) { + // Hash args := []interface{}{"MD4", "123456"} res := gou.NewProcess("yao.crypto.hash", args...).Run() assert.Equal(t, "585028aa0f794af812ee3be8804eb14a", res) + + // HMac + args = append(args, "123456") + res = gou.NewProcess("yao.crypto.hmac", args...).Run() + assert.Equal(t, "356f45727db95d65843b2794474d741c", res) } func TestMD5(t *testing.T) { + // Hash args := []interface{}{"MD5", "123456"} res := gou.NewProcess("yao.crypto.hash", args...).Run() assert.Equal(t, "e10adc3949ba59abbe56e057f20f883e", res) + + // HMac + args = append(args, "123456") + res = gou.NewProcess("yao.crypto.hmac", args...).Run() + assert.Equal(t, "30ce71a73bdd908c3955a90e8f7429ef", res) } func TestSHA1(t *testing.T) { + // Hash args := []interface{}{"SHA1", "123456"} res := gou.NewProcess("yao.crypto.hash", args...).Run() assert.Equal(t, "7c4a8d09ca3762af61e59520943dc26494f8941b", res) + + // HMac + args = append(args, "123456") + res = gou.NewProcess("yao.crypto.hmac", args...).Run() + assert.Equal(t, "74b55b6ab2b8e438ac810435e369e3047b3951d0", res) } func TestSHA256(t *testing.T) { + // Hash args := []interface{}{"SHA256", "123456"} res := gou.NewProcess("yao.crypto.hash", args...).Run() assert.Equal(t, "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", res) + + // HMac + args = append(args, "123456") + res = gou.NewProcess("yao.crypto.hmac", args...).Run() + assert.Equal(t, "b8ad08a3a547e35829b821b75370301dd8c4b06bdd7771f9b541a75914068718", res) } diff --git a/crypto/process.go b/crypto/process.go index d58f8297..ee90980a 100644 --- a/crypto/process.go +++ b/crypto/process.go @@ -7,6 +7,7 @@ import ( func init() { gou.RegisterProcessHandler("yao.crypto.hash", ProcessHash) + gou.RegisterProcessHandler("yao.crypto.hmac", ProcessHmac) } // ProcessHash yao.crypto.hash Crypto Hash @@ -28,3 +29,25 @@ func ProcessHash(process *gou.Process) interface{} { } return res } + +// ProcessHmac yao.crypto.hmac Crypto the Keyed-Hash Message Authentication Code (HMAC) Hash +// Args[0] string: the hash function name. MD4/MD5/SHA1/SHA224/SHA256/SHA384/SHA512/MD5SHA1/RIPEMD160/SHA3_224/SHA3_256/SHA3_384/SHA3_512/SHA512_224/SHA512_256/BLAKE2s_256/BLAKE2b_256/BLAKE2b_384/BLAKE2b_512 +// Args[1] string: value +// Args[2] string: key +func ProcessHmac(process *gou.Process) interface{} { + process.ValidateArgNums(2) + typ := process.ArgsString(0) + value := process.ArgsString(1) + key := process.ArgsString(2) + + h, has := hashTypes[typ] + if !has { + exception.New("%s does not support", 400, typ).Throw() + } + + res, err := Hmac(h, value, key) + if err != nil { + exception.New("%s error: %s value: %s", 400, typ, err, value).Throw() + } + return res +} From 1aa82b7e5e1d4a6358f92479e2d1729ce2ee8ae6 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 13 Jul 2022 21:49:57 +0800 Subject: [PATCH 67/96] [add] yao.crypto.hmac base64 encoding --- crypto/crypto.go | 8 +++++++- crypto/crypto_test.go | 12 ++++++++++++ crypto/process.go | 12 +++++++++--- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/crypto/crypto.go b/crypto/crypto.go index a0dddcda..96f99954 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -3,6 +3,7 @@ package crypto import ( "crypto" "crypto/hmac" + "encoding/base64" "fmt" "golang.org/x/crypto/md4" @@ -45,11 +46,16 @@ func Hash(hash crypto.Hash, value string) (string, error) { } // Hmac the Keyed-Hash Message Authentication Code (HMAC) -func Hmac(hash crypto.Hash, value string, key string) (string, error) { +func Hmac(hash crypto.Hash, value string, key string, encoding ...string) (string, error) { mac := hmac.New(hash.New, []byte(key)) _, err := mac.Write([]byte(value)) if err != nil { return "", err } + + if len(encoding) > 0 && encoding[0] == "base64" { + return base64.StdEncoding.EncodeToString(mac.Sum(nil)), nil + } + return fmt.Sprintf("%x", mac.Sum(nil)), nil } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index a1552942..2a85bbd4 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -54,3 +54,15 @@ func TestSHA256(t *testing.T) { res = gou.NewProcess("yao.crypto.hmac", args...).Run() assert.Equal(t, "b8ad08a3a547e35829b821b75370301dd8c4b06bdd7771f9b541a75914068718", res) } + +func TestSHA1Base64(t *testing.T) { + // Hash + args := []interface{}{"SHA1", "123456"} + res := gou.NewProcess("yao.crypto.hash", args...).Run() + assert.Equal(t, "7c4a8d09ca3762af61e59520943dc26494f8941b", res) + + // HMac + args = append(args, "123456", "base64") + res = gou.NewProcess("yao.crypto.hmac", args...).Run() + assert.Equal(t, "dLVbarK45DisgQQ142njBHs5UdA=", res) +} diff --git a/crypto/process.go b/crypto/process.go index ee90980a..235950cb 100644 --- a/crypto/process.go +++ b/crypto/process.go @@ -14,7 +14,7 @@ func init() { // Args[0] string: the hash function name. MD4/MD5/SHA1/SHA224/SHA256/SHA384/SHA512/MD5SHA1/RIPEMD160/SHA3_224/SHA3_256/SHA3_384/SHA3_512/SHA512_224/SHA512_256/BLAKE2s_256/BLAKE2b_256/BLAKE2b_384/BLAKE2b_512 // Args[1] string: value func ProcessHash(process *gou.Process) interface{} { - process.ValidateArgNums(1) + process.ValidateArgNums(2) typ := process.ArgsString(0) value := process.ArgsString(1) @@ -34,8 +34,9 @@ func ProcessHash(process *gou.Process) interface{} { // Args[0] string: the hash function name. MD4/MD5/SHA1/SHA224/SHA256/SHA384/SHA512/MD5SHA1/RIPEMD160/SHA3_224/SHA3_256/SHA3_384/SHA3_512/SHA512_224/SHA512_256/BLAKE2s_256/BLAKE2b_256/BLAKE2b_384/BLAKE2b_512 // Args[1] string: value // Args[2] string: key +// Args[3] string: base64 func ProcessHmac(process *gou.Process) interface{} { - process.ValidateArgNums(2) + process.ValidateArgNums(3) typ := process.ArgsString(0) value := process.ArgsString(1) key := process.ArgsString(2) @@ -45,7 +46,12 @@ func ProcessHmac(process *gou.Process) interface{} { exception.New("%s does not support", 400, typ).Throw() } - res, err := Hmac(h, value, key) + encoding := "" + if process.NumOfArgs() > 3 { + encoding = process.ArgsString(3) + } + + res, err := Hmac(h, value, key, encoding) if err != nil { exception.New("%s error: %s value: %s", 400, typ, err, value).Throw() } From 0f9590715c4e995f1afd2e85f861c43554c91081 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 14 Jul 2022 09:38:30 +0800 Subject: [PATCH 68/96] [change] production mode log level --- config/config.go | 2 +- engine/load.go | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/config/config.go b/config/config.go index 65ba54b6..99281cf6 100644 --- a/config/config.go +++ b/config/config.go @@ -60,7 +60,7 @@ func Load() Config { // Production 设定为生产环境 func Production() { Conf.Mode = "production" - log.SetLevel(log.ErrorLevel) + log.SetLevel(log.InfoLevel) log.SetFormatter(log.TEXT) if Conf.LogMode == "JSON" { log.SetFormatter(log.JSON) diff --git a/engine/load.go b/engine/load.go index 40b3568f..0521f5f0 100644 --- a/engine/load.go +++ b/engine/load.go @@ -55,43 +55,43 @@ func Load(cfg config.Config) (err error) { // 第四步: 加载共享库 & JS 处理器 err = share.Load(cfg) // 加载共享库 lib if err != nil { - log.Warn(err.Error()) + log.Debug(err.Error()) } err = script.Load(cfg) // 加载JS处理器 script if err != nil { - log.Warn(err.Error()) + log.Debug(err.Error()) } // 第五步: 加载数据模型等 err = model.Load(cfg) // 加载数据模型 model if err != nil { - log.Warn(err.Error()) + log.Debug(err.Error()) } err = flow.Load(cfg) // 加载业务逻辑 Flow if err != nil { - log.Warn(err.Error()) + log.Debug(err.Error()) } err = store.Load(cfg) // Load stores if err != nil { - log.Warn(err.Error()) + log.Debug(err.Error()) } err = plugin.Load(cfg) // 加载业务插件 plugin if err != nil { - log.Warn(err.Error()) + log.Debug(err.Error()) } err = table.Load(cfg) // 加载数据表格 table if err != nil { - log.Warn(err.Error()) + log.Debug(err.Error()) } err = chart.Load(cfg) // 加载分析图表 chart if err != nil { - log.Warn(err.Error()) + log.Debug(err.Error()) } page.Load(cfg) // 加载页面 page 忽略错误 @@ -100,7 +100,7 @@ func Load(cfg config.Config) (err error) { err = api.Load(cfg) // 加载业务接口 API if err != nil { - log.Warn(err.Error()) + log.Debug(err.Error()) } server.Load(cfg) // 加载服务 From 24e9a576ce916e35aeb41f46797ce900f94d481d Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 14 Jul 2022 20:03:19 +0800 Subject: [PATCH 69/96] [add] refactor socket client --- tests/scripts/socket.js | 34 +++++++++++++++++++++++++++++ tests/servers/rfid.sock.json | 15 ++++++++----- tests/servers/rfid_client.sock.json | 17 ++++++++++----- 3 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 tests/scripts/socket.js diff --git a/tests/scripts/socket.js b/tests/scripts/socket.js new file mode 100644 index 00000000..c22fc669 --- /dev/null +++ b/tests/scripts/socket.js @@ -0,0 +1,34 @@ +function onData(data, recvLen) { + console.log(`Data: ${data} ${recvLen}`); + log.Trace("onData: %v %v", data, recvLen); +} + +function onError(err) { + console.log(`Error: ${err} `); +} + +function onClosed(data, err) { + console.log(`Closed: ${data} ${err} `); +} + +function onConnected(option) { + console.log("onConnected", option); +} + +// Convert a hex string to a byte array +function hexToBytes(hex) { + for (var bytes = [], c = 0; c < hex.length; c += 2) { + bytes.push(parseInt(hex.substr(c, 2), 16)); + } + return bytes; +} + +// Convert a byte array to a hex string +function bytesToHex(bytes) { + for (var hex = [], i = 0; i < bytes.length; i++) { + var current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i]; + hex.push((current >>> 4).toString(16)); + hex.push((current & 0xf).toString(16)); + } + return hex.join(""); +} diff --git a/tests/servers/rfid.sock.json b/tests/servers/rfid.sock.json index e2716f16..1da22f0a 100644 --- a/tests/servers/rfid.sock.json +++ b/tests/servers/rfid.sock.json @@ -1,12 +1,17 @@ { "name": "RFID Receiver (Server Mode)", - "description": "RFID Receiver (Server Mode)", + "description": "RFID Receiver", "mode": "server", - "version": "0.9.2", + "version": "0.0.1", "protocol": "tcp", "host": "0.0.0.0", "port": "3019", - "process": "flows.rfid.read", - "buffer": 60, - "keep": 0 + "event": { + "data": "scripts.socket.onData", + "closed": "scripts.socket.onClosed", + "connected": "scripts.socket.onConnected", + "error": "scripts.socket.onError" + }, + "keep": 0, + "buffer": 60 } diff --git a/tests/servers/rfid_client.sock.json b/tests/servers/rfid_client.sock.json index 7889de6e..a66fdda7 100644 --- a/tests/servers/rfid_client.sock.json +++ b/tests/servers/rfid_client.sock.json @@ -1,12 +1,19 @@ { "name": "RFID Receiver (Client Mode)", - "description": "RFID Receiver (Client Mode)", + "description": "RFID Receiver", "mode": "client", "version": "0.9.2", "protocol": "tcp", - "host": "192.168.1.192", - "port": "6000", - "process": "flows.rfid.read", + "host": "192.168.31.33", + "port": "3019", + "event": { + "data": "scripts.socket.onData", + "closed": "scripts.socket.onClosed", + "connected": "scripts.socket.onConnected", + "error": "scripts.socket.onError" + }, "buffer": 60, - "keep": 0 + "keep": 0, + "attempts": 9, + "attempt_after": 10 } From 200fc22e46990e73cdf0152f0b8656de96d9f0a0 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 14 Jul 2022 22:19:14 +0800 Subject: [PATCH 70/96] [add] yao socket command --- cmd/root.go | 1 + cmd/socket.go | 95 +++++++++++++++++++++++++++++++++++++++++++ cmd/start.go | 2 +- server/process.go | 46 ++++++++++++--------- server/socket_test.go | 12 +++--- 5 files changed, 129 insertions(+), 27 deletions(-) create mode 100644 cmd/socket.go diff --git a/cmd/root.go b/cmd/root.go index 5760724a..cd429a7f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -94,6 +94,7 @@ func init() { serviceCmd, dumpCmd, restoreCmd, + socketCmd, ) // rootCmd.SetHelpCommand(helpCmd) rootCmd.PersistentFlags().StringVarP(&appPath, "app", "a", "", L("Application directory")) diff --git a/cmd/socket.go b/cmd/socket.go new file mode 100644 index 00000000..563c63bc --- /dev/null +++ b/cmd/socket.go @@ -0,0 +1,95 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + jsoniter "github.com/json-iterator/go" + "github.com/spf13/cobra" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/engine" + "github.com/yaoapp/yao/share" +) + +var socketCmd = &cobra.Command{ + Use: "socket", + Short: L("Open socket"), + Long: L("Open socket"), + Run: func(cmd *cobra.Command, args []string) { + defer share.SessionStop() + defer gou.KillPlugins() + defer func() { + err := exception.Catch(recover()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + }() + + Boot() + cfg := config.Conf + cfg.Session.IsCLI = true + engine.Load(cfg) + if len(args) < 1 { + fmt.Println(color.RedString(L("Not enough arguments"))) + fmt.Println(color.WhiteString(share.BUILDNAME + " help")) + return + } + + name := args[0] + pargs := []interface{}{} + for i, arg := range args { + if i == 0 { + continue + } + + // 解析参数 + if strings.HasPrefix(arg, "::") { + arg := strings.TrimPrefix(arg, "::") + var v interface{} + err := jsoniter.Unmarshal([]byte(arg), &v) + if err != nil { + fmt.Println(color.RedString(L("Arguments: %s"), err.Error())) + return + } + pargs = append(pargs, v) + fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg)) + } else if strings.HasPrefix(arg, "\\::") { + arg := "::" + strings.TrimPrefix(arg, "\\::") + pargs = append(pargs, arg) + fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg)) + } else { + pargs = append(pargs, arg) + fmt.Println(color.WhiteString("args[%d]: %s", i-1, arg)) + } + + } + + socket, has := gou.Sockets[name] + if !has { + fmt.Println(color.RedString(L("%s not exists!"), name)) + return + } + + if socket.Mode != "client" { + fmt.Println(color.RedString(L("%s not supported yet!"), socket.Mode)) + return + } + + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(socket.Name)) + fmt.Println(color.WhiteString("---------------------------------")) + fmt.Println(color.GreenString("Mode: %s", socket.Mode)) + fmt.Println(color.GreenString("Host: %s://%s", socket.Protocol, socket.Host)) + fmt.Println(color.GreenString("Port: %s", socket.Port)) + fmt.Println(color.WhiteString("--------------------------------------")) + err := socket.Open(pargs...) + if err != nil { + fmt.Println(color.RedString(L("%s"), err.Error())) + return + } + + }, +} diff --git a/cmd/start.go b/cmd/start.go index 9d4f1255..052164a7 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -107,7 +107,7 @@ var startCmd = &cobra.Command{ if sock.Mode == "server" { go sock.Start() } else if sock.Mode == "client" { - go sock.Connect() + go sock.Open() } } } diff --git a/server/process.go b/server/process.go index 8cd7ce37..d0b02b8b 100644 --- a/server/process.go +++ b/server/process.go @@ -6,8 +6,8 @@ import ( ) func init() { - gou.RegisterProcessHandler("xiang.server.Start", ProcessStart) - gou.RegisterProcessHandler("xiang.server.Connect", ProcessConnect) + // gou.RegisterProcessHandler("xiang.server.Start", ProcessStart) + // gou.RegisterProcessHandler("xiang.server.Open", ProcessOpen) } // ProcessStart xiang.server.Start @@ -33,25 +33,31 @@ func ProcessStart(process *gou.Process) interface{} { return nil } -// ProcessConnect xiang.server.Connect -func ProcessConnect(process *gou.Process) interface{} { - process.ValidateArgNums(1) +// ProcessOpen xiang.server.Open +// func ProcessOpen(process *gou.Process) interface{} { +// process.ValidateArgNums(1) - name := process.ArgsString(0) - serv, has := gou.Sockets[name] - if !has { - exception.New("%s does not load", 400, name).Throw() - } +// name := process.ArgsString(0) +// serv, has := gou.Sockets[name] +// if !has { +// exception.New("%s does not load", 400, name).Throw() +// return nil +// } - args := []interface{}{} - if process.NumOfArgs() > 1 { - args = process.Args[1:] - } +// args := []interface{}{} +// if process.NumOfArgs() > 1 { +// args = process.Args[1:] +// } - if serv.Mode != "client" { - exception.New("%s mode [%s] not server", 400, name, serv.Mode).Throw() - } +// if serv.Mode != "client" { +// exception.New("%s mode [%s] should be client", 400, name, serv.Mode).Throw() +// return nil +// } - serv.Connect(args...) - return nil -} +// err := serv.Open(args...) +// if err != nil { +// exception.New("%s: %s", 500, name, err.Error()).Throw() +// } + +// return nil +// } diff --git a/server/socket_test.go b/server/socket_test.go index 05af1a9d..ecb73c82 100644 --- a/server/socket_test.go +++ b/server/socket_test.go @@ -21,12 +21,12 @@ func TestProcessStart(t *testing.T) { // }) } -func TestProcessConnect(t *testing.T) { - Load(config.Conf) - // assert.NotPanics(t, func() { - // gou.NewProcess("xiang.server.Connect", "rfid_client").Run() - // }) -} +// func TestProcessConnect(t *testing.T) { +// Load(config.Conf) +// // assert.NotPanics(t, func() { +// // gou.NewProcess("xiang.server.Connect", "rfid_client").Run() +// // }) +// } func check(t *testing.T) { keys := []string{} From ee40974f07f4f2d818d9c17fa4ed4843d7150e89 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 16 Jul 2022 17:11:03 +0800 Subject: [PATCH 71/96] [add] ssl.Sign & ssl.Verify processes --- cert/cert.go | 35 +++++++++++++++++++++++++++++++ cert/cert_test.go | 46 +++++++++++++++++++++++++++++++++++++++++ engine/load.go | 7 +++++++ tests/certs/cert.pem | 19 +++++++++++++++++ tests/certs/private.pem | 28 +++++++++++++++++++++++++ tests/certs/public.pem | 9 ++++++++ 6 files changed, 144 insertions(+) create mode 100644 cert/cert.go create mode 100644 cert/cert_test.go create mode 100644 tests/certs/cert.pem create mode 100644 tests/certs/private.pem create mode 100644 tests/certs/public.pem diff --git a/cert/cert.go b/cert/cert.go new file mode 100644 index 00000000..3ecffc3a --- /dev/null +++ b/cert/cert.go @@ -0,0 +1,35 @@ +package cert + +import ( + "fmt" + "path/filepath" + + "github.com/yaoapp/gou/ssl" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +// Load 加载API +func Load(cfg config.Config) error { + var root = filepath.Join(cfg.Root, "certs") + return LoadFrom(root, "") +} + +// LoadFrom 从特定目录加载 +func LoadFrom(dir string, prefix string) error { + + if share.DirNotExists(dir) { + return fmt.Errorf("%s does not exists", dir) + } + + err := share.Walk(dir, ".pem", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + _, err := ssl.LoadCertificateFrom(filename, name) + if err != nil { + log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + } + }) + + return err +} diff --git a/cert/cert_test.go b/cert/cert_test.go new file mode 100644 index 00000000..34cbdf23 --- /dev/null +++ b/cert/cert_test.go @@ -0,0 +1,46 @@ +package cert + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" + "github.com/yaoapp/gou/ssl" + "github.com/yaoapp/yao/config" +) + +func TestLoad(t *testing.T) { + Load(config.Conf) + LoadFrom("not a path", "404.") + check(t) +} + +func TestProcessSign(t *testing.T) { + Load(config.Conf) + args := []interface{}{"hello world", "private", "SHA256"} + signature, err := gou.NewProcess("ssl.Sign", args...).Exec() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "EDHf3C9TXEk7y8LzIk5czLefXZyGxcMDVMcbNuBBegDkTqnPsRQnhFtNOgCdox8lI3MzLatwjoljoMY4Qk+sHGd5mAHMpiREa1gRFSVYpA2xvXZ3+KsfOHAdICQrfUdy59QaJGo6iGPNGG8PQOXHPTVNn6LMfryat9+f4l21DPAZiT0RyCUgFZE3/Qv8Z/6J4AsIXMSKZD6BGPPHUxGe7UBrXZvcR5dX25EiNjuH2OO38YJnDiTRVw14UI5fk/mQrwRdezj5tSKFCyHt912BZExXtkHISiYFNTZ/2RhOup5Xx6o3GvrEOdshrnN80Lwu1Aaju+lnZp13hDz4P6hU7w==", signature) +} + +func TestProcessVerify(t *testing.T) { + Load(config.Conf) + signature := "EDHf3C9TXEk7y8LzIk5czLefXZyGxcMDVMcbNuBBegDkTqnPsRQnhFtNOgCdox8lI3MzLatwjoljoMY4Qk+sHGd5mAHMpiREa1gRFSVYpA2xvXZ3+KsfOHAdICQrfUdy59QaJGo6iGPNGG8PQOXHPTVNn6LMfryat9+f4l21DPAZiT0RyCUgFZE3/Qv8Z/6J4AsIXMSKZD6BGPPHUxGe7UBrXZvcR5dX25EiNjuH2OO38YJnDiTRVw14UI5fk/mQrwRdezj5tSKFCyHt912BZExXtkHISiYFNTZ/2RhOup5Xx6o3GvrEOdshrnN80Lwu1Aaju+lnZp13hDz4P6hU7w==" + args := []interface{}{"hello world", signature, "cert", "SHA256"} + res, err := gou.NewProcess("ssl.Verify", args...).Exec() + if err != nil { + t.Fatal(err) + } + + assert.True(t, res.(bool)) +} + +func check(t *testing.T) { + keys := []string{} + for key := range ssl.Certificates { + keys = append(keys, key) + } + assert.Equal(t, 3, len(keys)) +} diff --git a/engine/load.go b/engine/load.go index 0521f5f0..a51907c0 100644 --- a/engine/load.go +++ b/engine/load.go @@ -10,6 +10,7 @@ import ( "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/api" "github.com/yaoapp/yao/app" + "github.com/yaoapp/yao/cert" "github.com/yaoapp/yao/chart" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/flow" @@ -38,6 +39,12 @@ func Load(cfg config.Config) (err error) { gou.LoadCrypt(fmt.Sprintf(`{"key":"%s"}`, cfg.DB.AESKey), "AES") gou.LoadCrypt(`{}`, "PASSWORD") + // Load Certs + err = cert.Load(cfg) + if err != nil { + log.Debug(err.Error()) + } + // 第二步: 建立数据库 & 会话连接 share.DBConnect(cfg.DB) // 创建数据库连接 // share.SessionConnect(cfg.Session) // 创建会话服务器链接 diff --git a/tests/certs/cert.pem b/tests/certs/cert.pem new file mode 100644 index 00000000..18ae9586 --- /dev/null +++ b/tests/certs/cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCjCCAfICCQDIMbr1hVnv2TANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQGEwJD +TjEQMA4GA1UECAwHQmVpamluZzEQMA4GA1UEBwwHSGFpZGlhbjEUMBIGA1UECgwL +WWFvQXBwcy5jb20wHhcNMjIwNzE2MDY0ODI5WhcNMzIwNzEzMDY0ODI5WjBHMQsw +CQYDVQQGEwJDTjEQMA4GA1UECAwHQmVpamluZzEQMA4GA1UEBwwHSGFpZGlhbjEU +MBIGA1UECgwLWWFvQXBwcy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCqLwF385x1w0umsYpUb6LfSl/jUtny2T4hrVAAfzKcPDYyKT/gwJKJAm5v +PPLhsqjJLD27eRVNfzqCYOOJPIK5D+YAJigEb7/n+s96P7m0bzLZla3w/2GZRqZb +8m48xOQIHm/jVDC45Q3q/zdFUwgBtrOJxBBSs75eLRhj04CCPjdqoZcr0RdoyzSG +KvGiNGMNjfWEsdTnGYOcGNUa0UOsIpk+gS+QXD6EMlilM8GWQA+cDw6Owk5ydD0b +mPAiDD+mQn4wqCqruRuDziFe/cqWlY7etETT5Ox1D8eiLTTGRnj8p0TEro/eZ4Pc +JsVhH/t9yOvLgze3pgaPbbY6enTTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAp9 +7QuYnZF6YKp4zZp4VQkikAyYMvNnvQC9Fgah0RaX/ZY8hG7QQqiLDjcMPVaR13jT +oVumWJ225xO3HCi38AGHsJW8Uu7ZR4LZzI59MHenv10EIiTpc3HYAv+bH5bVtmgX +R7CEUMhNiJBKr6TwxiKBWkfjvO7sJrX4ZxKe6SKcOoKUeIMRyZCqstlSMlfu1xwr +DX83fyOyOaDJkUPIJNhVIbwqQxV0X47Bxdj9IdGF26ut4Dh0YNVxtArVr5va1VQo +Rsa9r63lhmnqbjeFcbY8TlO1Pm+hWgOmgcrjGNdP8DYi3KFMFiwY+VSUtwPnSZQS +OANAV1mlEv6+7UMZHRs= +-----END CERTIFICATE----- diff --git a/tests/certs/private.pem b/tests/certs/private.pem new file mode 100644 index 00000000..ae8df512 --- /dev/null +++ b/tests/certs/private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqLwF385x1w0um +sYpUb6LfSl/jUtny2T4hrVAAfzKcPDYyKT/gwJKJAm5vPPLhsqjJLD27eRVNfzqC +YOOJPIK5D+YAJigEb7/n+s96P7m0bzLZla3w/2GZRqZb8m48xOQIHm/jVDC45Q3q +/zdFUwgBtrOJxBBSs75eLRhj04CCPjdqoZcr0RdoyzSGKvGiNGMNjfWEsdTnGYOc +GNUa0UOsIpk+gS+QXD6EMlilM8GWQA+cDw6Owk5ydD0bmPAiDD+mQn4wqCqruRuD +ziFe/cqWlY7etETT5Ox1D8eiLTTGRnj8p0TEro/eZ4PcJsVhH/t9yOvLgze3pgaP +bbY6enTTAgMBAAECggEAGgj65ojkRLfN2QCMMwKpHhPYu4QvR5Goiapj9M+AxJoJ +iH77AL01LbSrhGUEmzajz7Lu9sE9Ww+9ubhs1s/lB77A5wDvWk5mQ20BVKhvFoV0 +eAkIBThsi/Bc/vAFoVJVuFA/ub1lfsO771l/8SEUtn5m1QqjnKNW2j133725iTMP +iwRlcEOEfFGa5bVQ/eEnQ5Fbk0OmIwfoSiFBTIsi2ZO7qTXaN5O1QzJKtSq0Jqbi +7cB0Z2cUi0QJ11jP4OcoawdVmx9InVwLkBfgR/AydmzClfnV7F8lk+m9UZmDV6/M +Wf+nAkijvYHvtmbb38qsHigE34agkvcy2YWoIstdcQKBgQDYi6fUsNu8RF2JC2NH +YWSnDS4JhYVP6pRugaH/RMe8+uX64JK1h85EXqf+rIWfmdkEN43p95lUxjObpEHE +pt2irrei8ARYqb7W0VkDfcEJnWoHMPlY/j7wunoIUFP1l8WWsrDyF3l+XnFLfRSj +rJ8/q/pPqJT2caYyOfF4EGltBQKBgQDJMOOVuoNAsZNttK9JwbORME+aK9OTXriT +VzHbqbMiyWBvqtnpRbwr6gHm+1nJgL4Fz1pT7vUxyXKzDIrRjx92qWp2sIFoYmAs +y0pdKChREbPAZUFIG5VJHqSgpStJq74Cq7Rrpmyg/rgKTNNCx7CwJ7Lap4OSr1tY +u5tZ/ARB9wKBgQCrXhn7X9sFKrU69yAiJRkaenusXVYa4HGG4l/pO+01yPiniYGQ +12CNCru395/npkDv7WcJeUCGrjO5QEwr7EIJtGrvLzLBV9QG/LWM5cAWc9SszD0I +CGyW5XwdiDRpbyqXD+KoM5uaPfM64kmrHbkJEniNAcShI1968BAADDziYQKBgHIG +UkXu0iqdOdgoWlwr+vBjeAoBosTYHzXqaih7sjoZ9Zz0MNKDolUlJ/TYKZgJbiju +ztvZB4AzEt2lUxvXyNZAyEUCXiVh8+4PPVM0yjzAEqcM/AW5FE+/nUAqvDDhtCvh +UrzcMp7AHmBAEE+9y0bbCQPhgrFkzxmoIjDKo0MbAoGAK58GTcH85twPkxdcskmv +wGzaUNXK4ggUvvXHbHDv0oVgx9aZR5dNclBkYcED0bQQL/c8aUk8hQkDj7TEvU6I +VDZ9iuJl2HRiVxRgl+cREqRhVhIoGbHCpZveymCIUjVWQqZtsWrtyI6R5gt+8LbW +eNJGBYZxsCZuBtBdhiFIlfI= +-----END PRIVATE KEY----- diff --git a/tests/certs/public.pem b/tests/certs/public.pem new file mode 100644 index 00000000..10edc968 --- /dev/null +++ b/tests/certs/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqi8Bd/OcdcNLprGKVG+i +30pf41LZ8tk+Ia1QAH8ynDw2Mik/4MCSiQJubzzy4bKoySw9u3kVTX86gmDjiTyC +uQ/mACYoBG+/5/rPej+5tG8y2ZWt8P9hmUamW/JuPMTkCB5v41QwuOUN6v83RVMI +AbazicQQUrO+Xi0YY9OAgj43aqGXK9EXaMs0hirxojRjDY31hLHU5xmDnBjVGtFD +rCKZPoEvkFw+hDJYpTPBlkAPnA8OjsJOcnQ9G5jwIgw/pkJ+MKgqq7kbg84hXv3K +lpWO3rRE0+TsdQ/Hoi00xkZ4/KdExK6P3meD3CbFYR/7fcjry4M3t6YGj222Onp0 +0wIDAQAB +-----END PUBLIC KEY----- From ef75f4cc4ad7fbd39d81c4456c9877b78cfcfc7a Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 16 Jul 2022 22:26:41 +0800 Subject: [PATCH 72/96] [change] websocket -> websocket server --- api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api.go b/api/api.go index 0de0ed78..91e7e3c3 100644 --- a/api/api.go +++ b/api/api.go @@ -36,7 +36,7 @@ func LoadFrom(dir string, prefix string) error { err = share.Walk(dir, ".ws.json", func(root, filename string) { name := prefix + share.SpecName(root, filename) content := share.ReadFile(filename) - _, err := gou.LoadWebSocket(string(content), name) + _, err := gou.LoadWebSocketServer(string(content), name) if err != nil { log.With(log.F{"root": root, "file": filename}).Error(err.Error()) } From d2cc885e3e4ac1b319abcb59c28d1e84b976f838 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 17 Jul 2022 08:22:50 +0800 Subject: [PATCH 73/96] [change] rename servers -> sockets --- cmd/socket.go | 19 +++++- cmd/start.go | 17 ----- engine/load.go | 12 ++-- server/process.go | 63 ------------------- {server => socket}/socket.go | 8 +-- {server => socket}/socket_test.go | 16 +---- tests/{servers => sockets}/rfid.sock.json | 0 .../rfid_client.sock.json | 0 8 files changed, 30 insertions(+), 105 deletions(-) delete mode 100644 server/process.go rename {server => socket}/socket.go (83%) rename {server => socket}/socket_test.go (50%) rename tests/{servers => sockets}/rfid.sock.json (100%) rename tests/{servers => sockets}/rfid_client.sock.json (100%) diff --git a/cmd/socket.go b/cmd/socket.go index 563c63bc..c4ffd400 100644 --- a/cmd/socket.go +++ b/cmd/socket.go @@ -78,12 +78,27 @@ var socketCmd = &cobra.Command{ return } + host := socket.Host + port := socket.Port + argsLen := len(pargs) + if argsLen > 0 { + if inputHost, ok := pargs[0].(string); ok { + host = inputHost + } + } + + if argsLen > 1 { + if inputPort, ok := pargs[1].(string); ok { + port = inputPort + } + } + fmt.Println(color.WhiteString("\n---------------------------------")) fmt.Println(color.WhiteString(socket.Name)) fmt.Println(color.WhiteString("---------------------------------")) fmt.Println(color.GreenString("Mode: %s", socket.Mode)) - fmt.Println(color.GreenString("Host: %s://%s", socket.Protocol, socket.Host)) - fmt.Println(color.GreenString("Port: %s", socket.Port)) + fmt.Println(color.GreenString("Host: %s://%s", socket.Protocol, host)) + fmt.Println(color.GreenString("Port: %s", port)) fmt.Println(color.WhiteString("--------------------------------------")) err := socket.Open(pargs...) if err != nil { diff --git a/cmd/start.go b/cmd/start.go index 052164a7..117e07ac 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -95,23 +95,6 @@ var startCmd = &cobra.Command{ service.Watch(config.Conf) } - // with the alpha features - if startAlpha { - for _, sock := range gou.Sockets { - fmt.Println(color.WhiteString("\n---------------------------------")) - fmt.Println(color.WhiteString(sock.Name)) - fmt.Println(color.WhiteString("---------------------------------")) - fmt.Println(color.GreenString("Mode: %s", sock.Mode)) - fmt.Println(color.GreenString("Host: %s://%s", sock.Protocol, sock.Host)) - fmt.Println(color.GreenString("Port: %s\n\n", sock.Port)) - if sock.Mode == "server" { - go sock.Start() - } else if sock.Mode == "client" { - go sock.Open() - } - } - } - fmt.Println(color.GreenString(L("✨LISTENING✨"))) service.Start() }, diff --git a/engine/load.go b/engine/load.go index a51907c0..4f11518b 100644 --- a/engine/load.go +++ b/engine/load.go @@ -20,11 +20,10 @@ import ( "github.com/yaoapp/yao/plugin" "github.com/yaoapp/yao/query" "github.com/yaoapp/yao/script" - "github.com/yaoapp/yao/server" "github.com/yaoapp/yao/share" + "github.com/yaoapp/yao/socket" "github.com/yaoapp/yao/store" "github.com/yaoapp/yao/table" - "github.com/yaoapp/yao/workflow" ) // Load 根据配置加载 API, FLow, Model, Plugin @@ -103,14 +102,19 @@ func Load(cfg config.Config) (err error) { page.Load(cfg) // 加载页面 page 忽略错误 importer.Load(cfg) // 加载数据导入 imports - workflow.Load(cfg) // 加载工作流 workflow + + // workflow.Load(cfg) // 加载工作流 workflow err = api.Load(cfg) // 加载业务接口 API if err != nil { log.Debug(err.Error()) } - server.Load(cfg) // 加载服务 + err = socket.Load(cfg) // Load sockets + if err != nil { + log.Debug(err.Error()) + } + return nil } diff --git a/server/process.go b/server/process.go deleted file mode 100644 index d0b02b8b..00000000 --- a/server/process.go +++ /dev/null @@ -1,63 +0,0 @@ -package server - -import ( - "github.com/yaoapp/gou" - "github.com/yaoapp/kun/exception" -) - -func init() { - // gou.RegisterProcessHandler("xiang.server.Start", ProcessStart) - // gou.RegisterProcessHandler("xiang.server.Open", ProcessOpen) -} - -// ProcessStart xiang.server.Start -func ProcessStart(process *gou.Process) interface{} { - process.ValidateArgNums(1) - - name := process.ArgsString(0) - serv, has := gou.Sockets[name] - if !has { - exception.New("%s does not load", 400, name).Throw() - } - - args := []interface{}{} - if process.NumOfArgs() > 1 { - args = process.Args[1:] - } - - if serv.Mode != "server" { - exception.New("%s mode [%s] not server", 400, name, serv.Mode).Throw() - } - - serv.Start(args...) - return nil -} - -// ProcessOpen xiang.server.Open -// func ProcessOpen(process *gou.Process) interface{} { -// process.ValidateArgNums(1) - -// name := process.ArgsString(0) -// serv, has := gou.Sockets[name] -// if !has { -// exception.New("%s does not load", 400, name).Throw() -// return nil -// } - -// args := []interface{}{} -// if process.NumOfArgs() > 1 { -// args = process.Args[1:] -// } - -// if serv.Mode != "client" { -// exception.New("%s mode [%s] should be client", 400, name, serv.Mode).Throw() -// return nil -// } - -// err := serv.Open(args...) -// if err != nil { -// exception.New("%s: %s", 500, name, err.Error()).Throw() -// } - -// return nil -// } diff --git a/server/socket.go b/socket/socket.go similarity index 83% rename from server/socket.go rename to socket/socket.go index 48b07ea0..7a7e4723 100644 --- a/server/socket.go +++ b/socket/socket.go @@ -1,4 +1,4 @@ -package server +package socket import ( "fmt" @@ -11,9 +11,9 @@ import ( ) // Load 加载API -func Load(cfg config.Config) { - var root = filepath.Join(cfg.Root, "servers") - LoadFrom(root, "") +func Load(cfg config.Config) error { + var root = filepath.Join(cfg.Root, "sockets") + return LoadFrom(root, "") } // LoadFrom 从特定目录加载 diff --git a/server/socket_test.go b/socket/socket_test.go similarity index 50% rename from server/socket_test.go rename to socket/socket_test.go index ecb73c82..1f41a566 100644 --- a/server/socket_test.go +++ b/socket/socket_test.go @@ -1,4 +1,4 @@ -package server +package socket import ( "testing" @@ -14,20 +14,6 @@ func TestLoad(t *testing.T) { check(t) } -func TestProcessStart(t *testing.T) { - Load(config.Conf) - // assert.NotPanics(t, func() { - // gou.NewProcess("xiang.server.Start", "rfid").Run() - // }) -} - -// func TestProcessConnect(t *testing.T) { -// Load(config.Conf) -// // assert.NotPanics(t, func() { -// // gou.NewProcess("xiang.server.Connect", "rfid_client").Run() -// // }) -// } - func check(t *testing.T) { keys := []string{} for key := range gou.Sockets { diff --git a/tests/servers/rfid.sock.json b/tests/sockets/rfid.sock.json similarity index 100% rename from tests/servers/rfid.sock.json rename to tests/sockets/rfid.sock.json diff --git a/tests/servers/rfid_client.sock.json b/tests/sockets/rfid_client.sock.json similarity index 100% rename from tests/servers/rfid_client.sock.json rename to tests/sockets/rfid_client.sock.json From ae4529009e866638c376a9d86e0ca7ed3cb08cf1 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 17 Jul 2022 09:08:57 +0800 Subject: [PATCH 74/96] [add] websocket command --- cmd/root.go | 1 + cmd/socket.go | 4 +- cmd/start.go | 2 +- cmd/websocket.go | 71 ++++++++++++++++++++++++++ engine/load.go | 6 +++ tests/scripts/websocket.js | 86 ++++++++++++++++++++++++++++++++ tests/websockets/message.ws.json | 18 +++++++ websocket/websocket.go | 36 +++++++++++++ websocket/websocket_test.go | 69 +++++++++++++++++++++++++ 9 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 cmd/websocket.go create mode 100644 tests/websockets/message.ws.json create mode 100644 websocket/websocket.go create mode 100644 websocket/websocket_test.go diff --git a/cmd/root.go b/cmd/root.go index cd429a7f..eec2ca8c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -95,6 +95,7 @@ func init() { dumpCmd, restoreCmd, socketCmd, + websocketCmd, ) // rootCmd.SetHelpCommand(helpCmd) rootCmd.PersistentFlags().StringVarP(&appPath, "app", "a", "", L("Application directory")) diff --git a/cmd/socket.go b/cmd/socket.go index c4ffd400..fd5a1948 100644 --- a/cmd/socket.go +++ b/cmd/socket.go @@ -16,8 +16,8 @@ import ( var socketCmd = &cobra.Command{ Use: "socket", - Short: L("Open socket"), - Long: L("Open socket"), + Short: L("Open a socket connection"), + Long: L("Open a socket connection"), Run: func(cmd *cobra.Command, args []string) { defer share.SessionStop() defer gou.KillPlugins() diff --git a/cmd/start.go b/cmd/start.go index 117e07ac..33a73f1e 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -47,7 +47,7 @@ var startCmd = &cobra.Command{ host = "127.0.0.1" } - if mode == "development" { + if mode == "development" || mode == "production" { fmt.Println(color.WhiteString("\n---------------------------------")) fmt.Println(color.WhiteString(L("API List"))) fmt.Println(color.WhiteString("---------------------------------")) diff --git a/cmd/websocket.go b/cmd/websocket.go new file mode 100644 index 00000000..69cc66d8 --- /dev/null +++ b/cmd/websocket.go @@ -0,0 +1,71 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/engine" + "github.com/yaoapp/yao/share" +) + +var websocketCmd = &cobra.Command{ + Use: "websocket", + Short: L("Open a websocket connection"), + Long: L("Open a websocket connection"), + Run: func(cmd *cobra.Command, args []string) { + defer share.SessionStop() + defer gou.KillPlugins() + defer func() { + err := exception.Catch(recover()) + if err != nil { + fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) + } + }() + + Boot() + cfg := config.Conf + cfg.Session.IsCLI = true + engine.Load(cfg) + if len(args) < 1 { + fmt.Println(color.RedString(L("Not enough arguments"))) + fmt.Println(color.WhiteString(share.BUILDNAME + " help")) + return + } + + name := args[0] + websocket, has := gou.WebSockets[name] + if !has { + fmt.Println(color.RedString(L("%s not exists!"), name)) + return + } + + url := websocket.URL + protocols := websocket.Protocols + argsLen := len(args) + if argsLen > 1 { + url = args[1] + } + + if argsLen > 2 { + protocols = args[2:] + } + + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(websocket.Name)) + fmt.Println(color.WhiteString("---------------------------------")) + fmt.Println(color.GreenString(" URL: %s", url)) + fmt.Println(color.GreenString("Protocols: %s", strings.Join(protocols, ","))) + fmt.Println(color.WhiteString("--------------------------------------")) + pargs := append([]string{url}, protocols...) + err := websocket.Open(pargs...) + if err != nil { + fmt.Println(color.RedString(L("%s"), err.Error())) + return + } + }, +} diff --git a/engine/load.go b/engine/load.go index 4f11518b..00e9b871 100644 --- a/engine/load.go +++ b/engine/load.go @@ -24,6 +24,7 @@ import ( "github.com/yaoapp/yao/socket" "github.com/yaoapp/yao/store" "github.com/yaoapp/yao/table" + "github.com/yaoapp/yao/websocket" ) // Load 根据配置加载 API, FLow, Model, Plugin @@ -115,6 +116,11 @@ func Load(cfg config.Config) (err error) { log.Debug(err.Error()) } + err = websocket.Load(cfg) // Load websockets (client) + if err != nil { + log.Debug(err.Error()) + } + return nil } diff --git a/tests/scripts/websocket.js b/tests/scripts/websocket.js index 557929e3..dd5182ba 100644 --- a/tests/scripts/websocket.js +++ b/tests/scripts/websocket.js @@ -3,3 +3,89 @@ function Hello() { var response = ws.push("Hello World"); return response; } + +const host = "127.0.0.1:5099"; + +/** + * WebSocket Client EventMode(daemon) + */ +function Event() { + var url = `ws://${host}/websocket/chat`; + var ws = new WebSocket(url, "yao-chat-01"); + + // Connection opened + ws.on("open", function (event) { + ws.send("Hello Server!", event.data); + }); + + // Listen for messages + ws.on("message", function (event) { + console.log("Message from server ", event.data); + ws.close(200, "Bye"); + }); + + // Listen for error + ws.on("error", (event) => { + console.log("Message from server ", event); + }); + + // Listen for close + ws.on("close", (event) => { + console.log("The connection has been closed successfully"); + }); +} + +/** + * WebSocket Client PushMode + */ +function Push() { + var url = `ws://${host}/websocket/chat`; + var ws = new WebSocket(url, "yao-chat-01"); + var message = ws.push("Hello Server!"); + console.log(message); +} + +/** + * WebSocket Client JWT Auth + */ +function Token() { + var token = "xxx"; + var url = `ws://${host}/websocket/chat?token=${token}`; + var ws = new WebSocket(url, "yao-chat-01"); + var message = ws.push("Hello Server!"); + console.log(message); +} + +/** + * WebSocket Client Basic Auth + */ +function Basic() { + // Basic Auth username:password + var user = "test"; + var password = "WsTest123**"; + var url = `ws://${user}:${password}@${host}/websocket/chat?token=${token}`; + var ws = new WebSocket(url, "yao-chat-01"); + var message = ws.push("Hello Server!"); + console.log(message); +} + +function onData(data, recvLen) { + console.log(`Data: ${data} ${recvLen}`); + log.Trace("onData: %v %v", data, recvLen); + if (data[0] == "1") { + Process("websocket.Close", "message"); + } +} + +function onError(err) { + console.log(`Error: ${err} `); +} + +function onClosed(data, err) { + console.log(`Closed: ${data} ${err} `); +} + +function onConnected(option) { + console.log("onConnected", option); + Process("websocket.Write", "message", "1|Hello World"); +} diff --git a/tests/websockets/message.ws.json b/tests/websockets/message.ws.json new file mode 100644 index 00000000..0ef04577 --- /dev/null +++ b/tests/websockets/message.ws.json @@ -0,0 +1,18 @@ +{ + "name": "A WebSocket client", + "description": "A Chat WebSocket client", + "version": "0.10.0", + "url": "ws://127.0.0.1:5011/websocket/message", + "protocols": ["yao-message-01"], + "buffer": { "read": 1024, "write": 1024 }, + "limit": { "read-wait": 10, "pong-wait": 20, "max-message": 1024 }, + "timeout": 5, + "attempt_after": 200, + "attempts": 9, + "event": { + "data": "scripts.websocket.onData", + "closed": "scripts.websocket.onClosed", + "connected": "scripts.websocket.onConnected", + "error": "scripts.websocket.onError" + } +} diff --git a/websocket/websocket.go b/websocket/websocket.go new file mode 100644 index 00000000..860a0c6d --- /dev/null +++ b/websocket/websocket.go @@ -0,0 +1,36 @@ +package websocket + +import ( + "fmt" + "path/filepath" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +// Load 加载API +func Load(cfg config.Config) error { + var root = filepath.Join(cfg.Root, "websockets") + return LoadFrom(root, "") +} + +// LoadFrom 从特定目录加载 +func LoadFrom(dir string, prefix string) error { + + if share.DirNotExists(dir) { + return fmt.Errorf("%s does not exists", dir) + } + + err := share.Walk(dir, ".ws.json", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + _, err := gou.LoadWebSocket(string(content), name) + if err != nil { + log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + } + }) + + return err +} diff --git a/websocket/websocket_test.go b/websocket/websocket_test.go new file mode 100644 index 00000000..2c5c48a7 --- /dev/null +++ b/websocket/websocket_test.go @@ -0,0 +1,69 @@ +package websocket + +import ( + "fmt" + "net" + "net/http" + "testing" + "time" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" + "github.com/yaoapp/gou/websocket" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/script" +) + +func TestLoad(t *testing.T) { + Load(config.Conf) + LoadFrom("not a path", "404.") + check(t) +} + +func TestWebSocketOpen(t *testing.T) { + Load(config.Conf) + script.Load(config.Conf) + srv, url := serve(t) + defer srv.Stop() + + ws := gou.SelectWebSocket("message") + err := ws.Open(url, "messageV2", "chatV3") + if err != nil { + t.Fatal(err) + } +} + +func serve(t *testing.T) (*websocket.Upgrader, string) { + + ws, err := websocket.NewUpgrader("test") + if err != nil { + t.Fatalf("%s", err) + } + + gin.SetMode(gin.ReleaseMode) + router := gin.Default() + ws.SetHandler(func(message []byte) ([]byte, error) { return message, nil }) + ws.SetRouter(router) + + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + + go ws.Start() + go func() { + http.Serve(listener, router) + }() + time.Sleep(200 * time.Millisecond) + + return ws, fmt.Sprintf("ws://127.0.0.1:%d/websocket/test", listener.Addr().(*net.TCPAddr).Port) +} + +func check(t *testing.T) { + keys := []string{} + for key := range gou.WebSockets { + keys = append(keys, key) + } + assert.Equal(t, 1, len(keys)) +} From 6f3e92a170b4c8fc214131716258533cf9e83efd Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 17 Jul 2022 16:12:48 +0800 Subject: [PATCH 75/96] [add] yao.system.Exec process --- main.go | 1 + system/process.go | 42 ++++++++++++++++++++++++++++++++++++++++++ system/process_test.go | 16 ++++++++++++++++ system/system.go | 4 ++++ 4 files changed, 63 insertions(+) create mode 100644 system/process.go create mode 100644 system/process_test.go create mode 100644 system/system.go diff --git a/main.go b/main.go index 2cf755e5..64609582 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( _ "github.com/yaoapp/yao/crypto" _ "github.com/yaoapp/yao/helper" _ "github.com/yaoapp/yao/network" + _ "github.com/yaoapp/yao/system" _ "github.com/yaoapp/yao/user" _ "github.com/yaoapp/yao/xfs" ) diff --git a/system/process.go b/system/process.go new file mode 100644 index 00000000..555de4e5 --- /dev/null +++ b/system/process.go @@ -0,0 +1,42 @@ +package system + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" +) + +func init() { + gou.RegisterProcessHandler("yao.system.Exec", processExec) +} + +// processExec execute the system command +func processExec(process *gou.Process) interface{} { + process.ValidateArgNums(1) + cmd := process.ArgsString(0) + + _, err := exec.LookPath(cmd) + if err != nil { + exception.New("command %s not found: %s", 400, cmd, err.Error()).Throw() + return nil + } + + args := []string{} + for i, arg := range process.Args { + if i == 0 { + continue + } + args = append(args, fmt.Sprintf("%v", arg)) + } + + res, err := exec.Command(cmd, args...).Output() + if err != nil { + exception.New("command %s error: %s", 500, cmd, err.Error()).Throw() + return nil + } + + return strings.TrimRight(string(res), "\n") +} diff --git a/system/process_test.go b/system/process_test.go new file mode 100644 index 00000000..054fd6d0 --- /dev/null +++ b/system/process_test.go @@ -0,0 +1,16 @@ +package system + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" +) + +func TestProcessReturn(t *testing.T) { + output, err := gou.NewProcess("yao.system.Exec", "echo", "hello").Exec() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "hello", output) +} diff --git a/system/system.go b/system/system.go new file mode 100644 index 00000000..61e66e43 --- /dev/null +++ b/system/system.go @@ -0,0 +1,4 @@ +package system + +// Exec call system command +func Exec(cmd string, args ...string) {} From f3b2ca53ebb5e3b9b30d6e2d7ed2214086ce19a6 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 17 Jul 2022 22:07:47 +0800 Subject: [PATCH 76/96] [add] WebSocket server direct message support --- websocket/websocket_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/websocket/websocket_test.go b/websocket/websocket_test.go index 2c5c48a7..24d0df54 100644 --- a/websocket/websocket_test.go +++ b/websocket/websocket_test.go @@ -43,7 +43,7 @@ func serve(t *testing.T) (*websocket.Upgrader, string) { gin.SetMode(gin.ReleaseMode) router := gin.Default() - ws.SetHandler(func(message []byte) ([]byte, error) { return message, nil }) + ws.SetHandler(func(message []byte, id int) ([]byte, error) { return message, nil }) ws.SetRouter(router) listener, err := net.Listen("tcp", "127.0.0.1:0") From 5a92e4f7b539980e5b017b1c45f845e6b01a57d8 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 21 Jul 2022 12:07:42 +0800 Subject: [PATCH 77/96] [add] page session support --- page/process.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/page/process.go b/page/process.go index 27dfc1f1..21eb2e8e 100644 --- a/page/process.go +++ b/page/process.go @@ -33,6 +33,10 @@ func ProcessData(process *gou.Process) interface{} { } } + // with Session + page.Flow.WithGlobal(process.Global) + page.Flow.WithSID(process.Sid) + return page.GetData(params) } From 3509956bce649717b2f5d58fd7ef9a57f4f18e3d Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 30 Jul 2022 21:18:05 +0800 Subject: [PATCH 78/96] [add] Task & Schedule --- cmd/start.go | 2 +- engine/load.go | 12 +++++++ go.mod | 1 + go.sum | 3 ++ main_test.go | 1 + schedule/schedule.go | 36 +++++++++++++++++++++ schedule/schedule_test.go | 21 ++++++++++++ service/service.go | 24 +++++++------- task/task.go | 36 +++++++++++++++++++++ task/task_test.go | 19 +++++++++++ tests/schedules/mail.sch.json | 6 ++++ tests/schedules/sendmail.sch.json | 6 ++++ tests/scripts/mail.js | 53 +++++++++++++++++++++++++++++++ tests/tasks/mail.task.json | 16 ++++++++++ 14 files changed, 224 insertions(+), 12 deletions(-) create mode 100644 schedule/schedule.go create mode 100644 schedule/schedule_test.go create mode 100644 task/task.go create mode 100644 task/task_test.go create mode 100644 tests/schedules/mail.sch.json create mode 100644 tests/schedules/sendmail.sch.json create mode 100644 tests/scripts/mail.js create mode 100644 tests/tasks/mail.task.json diff --git a/cmd/start.go b/cmd/start.go index 33a73f1e..516d6362 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -24,7 +24,7 @@ var startCmd = &cobra.Command{ Short: L("Start Engine"), Long: L("Start Engine"), Run: func(cmd *cobra.Command, args []string) { - defer service.Stop(func() { fmt.Println(L("Service stopped")) }) + // defer service.Stop(func() { fmt.Println(L("Service stopped")) }) Boot() if startDebug { // 强制 debug 模式启动 diff --git a/engine/load.go b/engine/load.go index 00e9b871..7adb9167 100644 --- a/engine/load.go +++ b/engine/load.go @@ -19,11 +19,13 @@ import ( "github.com/yaoapp/yao/page" "github.com/yaoapp/yao/plugin" "github.com/yaoapp/yao/query" + "github.com/yaoapp/yao/schedule" "github.com/yaoapp/yao/script" "github.com/yaoapp/yao/share" "github.com/yaoapp/yao/socket" "github.com/yaoapp/yao/store" "github.com/yaoapp/yao/table" + "github.com/yaoapp/yao/task" "github.com/yaoapp/yao/websocket" ) @@ -121,6 +123,16 @@ func Load(cfg config.Config) (err error) { log.Debug(err.Error()) } + err = task.Load(cfg) // Load tasks + if err != nil { + log.Debug(err.Error()) + } + + err = schedule.Load(cfg) // Load schedules + if err != nil { + log.Debug(err.Error()) + } + return nil } diff --git a/go.mod b/go.mod index 344c8cd8..ac673cbd 100644 --- a/go.mod +++ b/go.mod @@ -76,6 +76,7 @@ require ( github.com/tidwall/rtred v0.1.2 // indirect github.com/tidwall/tinyqueue v0.1.1 // indirect github.com/ugorji/go/codec v1.1.7 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 // indirect golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect golang.org/x/mod v0.4.2 // indirect diff --git a/go.sum b/go.sum index bcd66b44..5f1c8c17 100644 --- a/go.sum +++ b/go.sum @@ -326,7 +326,10 @@ github.com/richardlehane/mscfb v1.0.3 h1:rD8TBkYWkObWO0oLDFCbwMeZ4KoalxQy+QgniCj github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o= github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/main_test.go b/main_test.go index e5c2a25a..8a00a058 100644 --- a/main_test.go +++ b/main_test.go @@ -122,6 +122,7 @@ func TestCommandStop(t *testing.T) { times++ res, err := request() if err != nil { + fmt.Println("REQUEST ERROR:", err) continue } assert.Equal(t, 1, any.Of(res.Get("id")).CInt()) diff --git a/schedule/schedule.go b/schedule/schedule.go new file mode 100644 index 00000000..70fe869e --- /dev/null +++ b/schedule/schedule.go @@ -0,0 +1,36 @@ +package schedule + +import ( + "fmt" + "path/filepath" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +// Load load schedule +func Load(cfg config.Config) error { + var root = filepath.Join(cfg.Root, "schedules") + return LoadFrom(root, "") +} + +// LoadFrom load from dir +func LoadFrom(dir string, prefix string) error { + + if share.DirNotExists(dir) { + return fmt.Errorf("%s does not exists", dir) + } + + err := share.Walk(dir, ".json", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + _, err := gou.LoadSchedule(string(content), name) + if err != nil { + log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + } + }) + + return err +} diff --git a/schedule/schedule_test.go b/schedule/schedule_test.go new file mode 100644 index 00000000..d0f27e3e --- /dev/null +++ b/schedule/schedule_test.go @@ -0,0 +1,21 @@ +package schedule + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/task" +) + +func TestLoad(t *testing.T) { + task.Load(config.Conf) + Load(config.Conf) + LoadFrom("not a path", "404.") + check(t) +} + +func check(t *testing.T) { + assert.Equal(t, 2, len(gou.Schedules)) +} diff --git a/service/service.go b/service/service.go index 31044161..5da3ca6d 100644 --- a/service/service.go +++ b/service/service.go @@ -6,8 +6,8 @@ import ( "github.com/yaoapp/yao/share" ) -var shutdown = make(chan bool) -var shutdownComplete = make(chan bool) +var shutdown = make(chan bool, 1) +var shutdownComplete = make(chan bool, 1) // Start 启动服务 func Start() error { @@ -25,7 +25,7 @@ func Start() error { Root: "/api", Allows: config.Conf.AllowFrom, }, - &shutdown, func(s gou.Server) { + shutdown, func(s gou.Server) { shutdownComplete <- true }, Middlewares...) @@ -44,7 +44,7 @@ func StartWithouttSession() { Root: "/api", Allows: config.Conf.AllowFrom, }, - &shutdown, func(s gou.Server) { + shutdown, func(s gou.Server) { shutdownComplete <- true }, Middlewares...) @@ -53,16 +53,18 @@ func StartWithouttSession() { // StopWithouttSession 关闭服务 func StopWithouttSession(onComplete func()) { shutdown <- true - <-shutdownComplete - gou.KillPlugins() - onComplete() + select { + case <-shutdownComplete: + onComplete() + } } // Stop 关闭服务 func Stop(onComplete func()) { shutdown <- true - <-shutdownComplete - share.SessionStop() - gou.KillPlugins() - onComplete() + select { + case <-shutdownComplete: + share.SessionStop() + onComplete() + } } diff --git a/task/task.go b/task/task.go new file mode 100644 index 00000000..07d36e7e --- /dev/null +++ b/task/task.go @@ -0,0 +1,36 @@ +package task + +import ( + "fmt" + "path/filepath" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +// Load load task +func Load(cfg config.Config) error { + var root = filepath.Join(cfg.Root, "tasks") + return LoadFrom(root, "") +} + +// LoadFrom load from dir +func LoadFrom(dir string, prefix string) error { + + if share.DirNotExists(dir) { + return fmt.Errorf("%s does not exists", dir) + } + + err := share.Walk(dir, ".json", func(root, filename string) { + name := prefix + share.SpecName(root, filename) + content := share.ReadFile(filename) + _, err := gou.LoadTask(string(content), name) + if err != nil { + log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + } + }) + + return err +} diff --git a/task/task_test.go b/task/task_test.go new file mode 100644 index 00000000..71f178a4 --- /dev/null +++ b/task/task_test.go @@ -0,0 +1,19 @@ +package task + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou/task" + "github.com/yaoapp/yao/config" +) + +func TestLoad(t *testing.T) { + Load(config.Conf) + LoadFrom("not a path", "404.") + check(t) +} + +func check(t *testing.T) { + assert.Equal(t, 1, len(task.Tasks)) +} diff --git a/tests/schedules/mail.sch.json b/tests/schedules/mail.sch.json new file mode 100644 index 00000000..fc9c1084 --- /dev/null +++ b/tests/schedules/mail.sch.json @@ -0,0 +1,6 @@ +{ + "name": "每分钟发送一封邮件", + "schedule": "*/1 * * * *", + "process": "scripts.mail.Send", + "args": [null, "$ENV.SEND_MAIL_TEST_MAIL"] +} diff --git a/tests/schedules/sendmail.sch.json b/tests/schedules/sendmail.sch.json new file mode 100644 index 00000000..b6d6e0bc --- /dev/null +++ b/tests/schedules/sendmail.sch.json @@ -0,0 +1,6 @@ +{ + "name": "每分钟发送一封邮件", + "schedule": "*/1 * * * *", + "task": "mail", + "args": ["$ENV.SEND_MAIL_TEST_MAIL"] +} diff --git a/tests/scripts/mail.js b/tests/scripts/mail.js new file mode 100644 index 00000000..978a5442 --- /dev/null +++ b/tests/scripts/mail.js @@ -0,0 +1,53 @@ +var id = 1024; + +/** + * Generate job id + * @returns + */ +function NextID() { + id = id + 1; + console.log(`NextID: ${id}`); + return id; +} + +function Send(id, mail, flag) { + for (var i = 1; i <= 3; i++) { + Process("xiang.system.Sleep", 200); + Process("tasks.mail.Progress", id, i, 3, "unit-test"); + } + if (flag) { + console.log(`flag: ${flag}`); + Process("xiang.system.Sleep", 2000); + } + console.log( + `Send: ${JSON.stringify({ foo: "bar", mail: mail, flag: flag || "-" })}` + ); + return { foo: "bar", mail: mail, flag: flag || "-" }; +} + +/** + * OnAdd add event + * @param {*} id + */ +function OnAdd(id) { + console.log(`OnAdd: #${id}`); +} + +/** + * OnProgress + * @param {*} id + * @param {*} current + * @param {*} total + * @param {*} message + */ +function OnProgress(id, current, total, message) { + console.log(`OnProgress: #${id} ${message} ${current}/${total} `); +} + +function OnSuccess(id, res) { + console.log(`OnSuccess: #${id} ${JSON.stringify(res)}`); +} + +function OnError(id, err) { + console.log(`OnError: #${id} ${err}`); +} diff --git a/tests/tasks/mail.task.json b/tests/tasks/mail.task.json new file mode 100644 index 00000000..5bf7bafd --- /dev/null +++ b/tests/tasks/mail.task.json @@ -0,0 +1,16 @@ +{ + "name": "发送邮件", + "worker_nums": "$ENV.SEND_MAIL_WORKER_NUMS", + "attempts": 3, + "attempt_after": 200, + "timeout": 2, + "size": 1000, + "process": "scripts.mail.Send", + "event": { + "next": "scripts.mail.NextID", + "add": "scripts.mail.OnAdd", + "success": "scripts.mail.OnSuccess", + "error": "scripts.mail.OnError", + "progress": "scripts.mail.OnProgress" + } +} From 8d88682967d48b2e45b280a4de8c30c28aadbb0c Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 5 Aug 2022 19:46:26 +0800 Subject: [PATCH 79/96] [add] Widget (alpha) --- engine/load.go | 6 + tests/dyforms/app.form.json | 58 ++++++++++ tests/dyforms/website.form.json | 77 +++++++++++++ tests/scripts/mail.js | 4 +- tests/widgets/README.md | 3 + tests/widgets/dyform/README.md | 3 + tests/widgets/dyform/assets/README.md | 5 + tests/widgets/dyform/compile.js | 107 ++++++++++++++++++ tests/widgets/dyform/export.js | 152 ++++++++++++++++++++++++++ tests/widgets/dyform/helper.js | 3 + tests/widgets/dyform/process.js | 24 ++++ tests/widgets/dyform/widget.json | 8 ++ widget/widget.go | 107 ++++++++++++++++++ widget/widget_test.go | 25 +++++ 14 files changed, 580 insertions(+), 2 deletions(-) create mode 100644 tests/dyforms/app.form.json create mode 100644 tests/dyforms/website.form.json create mode 100644 tests/widgets/README.md create mode 100644 tests/widgets/dyform/README.md create mode 100644 tests/widgets/dyform/assets/README.md create mode 100644 tests/widgets/dyform/compile.js create mode 100644 tests/widgets/dyform/export.js create mode 100644 tests/widgets/dyform/helper.js create mode 100644 tests/widgets/dyform/process.js create mode 100644 tests/widgets/dyform/widget.json create mode 100644 widget/widget.go create mode 100644 widget/widget_test.go diff --git a/engine/load.go b/engine/load.go index 7adb9167..c35bae19 100644 --- a/engine/load.go +++ b/engine/load.go @@ -27,6 +27,7 @@ import ( "github.com/yaoapp/yao/table" "github.com/yaoapp/yao/task" "github.com/yaoapp/yao/websocket" + "github.com/yaoapp/yao/widget" ) // Load 根据配置加载 API, FLow, Model, Plugin @@ -133,6 +134,11 @@ func Load(cfg config.Config) (err error) { log.Debug(err.Error()) } + err = widget.Load(cfg) // Load schedules + if err != nil { + log.Debug(err.Error()) + } + return nil } diff --git a/tests/dyforms/app.form.json b/tests/dyforms/app.form.json new file mode 100644 index 00000000..4e2b288d --- /dev/null +++ b/tests/dyforms/app.form.json @@ -0,0 +1,58 @@ +{ + "guard": "bearer-jwt", + "title": "应用表单", + "sn": "AP1024", + "orign": "手机应用", + "columns": [ + { + "name": "门店", + "field": "store_id", + "searchable": true, + "props": { + "value": ":store_id", + "type": "SelectStore", + "title": "门店", + "placeholder": "请选择门店", + "validation": [ + { "type": "Required" }, + { "type": "StoreStatus", "args": [":store_id"] } + ] + } + }, + { + "name": "订单数量", + "field": "orders_amount", + "searchable": true, + "props": { + "value": ":orders_amount", + "type": "Number", + "title": "订单数量", + "placeholder": "今日订单数量", + "computed": "OrderAmount", + "validation": [ + { "type": "Required" }, + { "type": "IsNumber", "args": [":orders_amount"] } + ] + } + }, + { + "name": "账单地址", + "field": "address", + "props": { + "value": ":address", + "type": "Text", + "title": "账单地址", + "placeholder": "填写账单地址", + "validation": [ + { "type": "Required" }, + { "type": "MaxLength", "args": [":address", 200] } + ] + } + } + ], + "filters": [], + "list-layout": { + "columns": [{ "name": "store_id" }, { "name": "orders_amount" }], + "filters": [{ "name": "订单数量" }] + } +} diff --git a/tests/dyforms/website.form.json b/tests/dyforms/website.form.json new file mode 100644 index 00000000..4dc5be25 --- /dev/null +++ b/tests/dyforms/website.form.json @@ -0,0 +1,77 @@ +{ + "guard": "bearer-jwt", + "title": "官网表单", + "sn": "AP2048", + "orign": "官方网站", + "columns": [ + { + "name": "门店", + "field": "store_id", + "searchable": true, + "props": { + "value": ":store_id", + "type": "SelectStore", + "title": "门店", + "placeholder": "请选择门店", + "validation": [ + { "type": "Required" }, + { "type": "StoreStatus", "args": [":store_id"] } + ] + } + }, + { + "name": "订单数量", + "field": "orders_amount", + "searchable": true, + "props": { + "value": ":orders_amount", + "type": "Number", + "title": "订单数量", + "placeholder": "今日订单数量", + "computed": "OrderAmount", + "validation": [ + { "type": "Required" }, + { "type": "IsNumber", "args": [":orders_amount"] } + ] + } + }, + { + "name": "账单地址", + "field": "address", + "props": { + "value": ":address", + "type": "Text", + "title": "账单地址", + "placeholder": "填写账单地址", + "validation": [ + { "type": "Required" }, + { "type": "MaxLength", "args": [":address", 200] } + ] + } + } + ], + "filters": [ + { + "订单数量": { + "label": "订单数量超过", + "bind": "where.orders_amount.ge", + "type": "Select", + "props": { + "placeholder": "订单数量", + "options": [ + { "label": "大于100", "value": 100 }, + { "label": "大于50", "value": 50 } + ] + } + } + } + ], + "list-layout": { + "columns": [ + { "name": "store_id" }, + { "name": "orders_amount" }, + { "name": "address" } + ], + "filters": [{ "name": "订单数量" }] + } +} diff --git a/tests/scripts/mail.js b/tests/scripts/mail.js index 978a5442..cc8a4fb4 100644 --- a/tests/scripts/mail.js +++ b/tests/scripts/mail.js @@ -12,12 +12,12 @@ function NextID() { function Send(id, mail, flag) { for (var i = 1; i <= 3; i++) { - Process("xiang.system.Sleep", 200); + Process("xiang.sys.Sleep", 200); Process("tasks.mail.Progress", id, i, 3, "unit-test"); } if (flag) { console.log(`flag: ${flag}`); - Process("xiang.system.Sleep", 2000); + Process("xiang.sys.Sleep", 2000); } console.log( `Send: ${JSON.stringify({ foo: "bar", mail: mail, flag: flag || "-" })}` diff --git a/tests/widgets/README.md b/tests/widgets/README.md new file mode 100644 index 00000000..18be262f --- /dev/null +++ b/tests/widgets/README.md @@ -0,0 +1,3 @@ +# Widgets + +## a customize low-code modules diff --git a/tests/widgets/dyform/README.md b/tests/widgets/dyform/README.md new file mode 100644 index 00000000..0c3894f7 --- /dev/null +++ b/tests/widgets/dyform/README.md @@ -0,0 +1,3 @@ +# DyForm + +## A form widget. users can design forms online diff --git a/tests/widgets/dyform/assets/README.md b/tests/widgets/dyform/assets/README.md new file mode 100644 index 00000000..226d49ab --- /dev/null +++ b/tests/widgets/dyform/assets/README.md @@ -0,0 +1,5 @@ +# Assets + +1. The assets folder's file will be copied to the `ui/.widgets//` folder. +2. If the folder does not exist will be created. To upgrade the assets, remove the `ui/.widgets//` folder. +3. The asset file PUBLIC URL is `http(s):///.widgets//`, ANYONE CAN VISIT DIRECTLY. diff --git a/tests/widgets/dyform/compile.js b/tests/widgets/dyform/compile.js new file mode 100644 index 00000000..2a4ec0b5 --- /dev/null +++ b/tests/widgets/dyform/compile.js @@ -0,0 +1,107 @@ +/** + * The DSL compiler. + * Translate the customize DSL to Models, Processes, Flows, Tables, etc. + */ + +/** + * Source + * Where to get the source of DSL + */ +function Source() { + return dyforms(); +} + +/** + * Compile + * Translate or extend the customize DSL + * @param {*} dsl + */ +function Compile(name, dsl) { + let newdsl = { name: name }; + return newdsl; +} + +/** + * OnLoad + * When the widget instance are loaded, the function will be called. + * For preparing the sources the widget need. + * @param {DSL} dsl + */ +function OnLoad(name, dsl) { + console.log(name, dsl); +} + +/** + * Migrate + * When the migrate command executes, the function will be called + * @param {DSL} dsl + * @param {bool} force + */ +function Migrate(dsl, force) {} + +/** + * Customize DSL + * @returns + */ +function dyforms() { + return { + pad: { + guard: "bearer-jwt", + title: "应用表单", + sn: "PAD1024", + orign: "手机平板", + columns: [ + { + name: "门店", + field: "store_id", + searchable: true, + props: { + value: ":store_id", + type: "SelectStore", + title: "门店", + placeholder: "请选择门店", + validation: [ + { type: "Required" }, + { type: "StoreStatus", args: [":store_id"] }, + ], + }, + }, + { + name: "订单数量", + field: "orders_amount", + searchable: true, + props: { + value: ":orders_amount", + type: "Number", + title: "订单数量", + placeholder: "今日订单数量", + computed: "OrderAmount", + validation: [ + { type: "Required" }, + { type: "IsNumber", args: [":orders_amount"] }, + ], + }, + }, + { + name: "账单地址", + field: "address", + props: { + value: ":address", + type: "Text", + title: "账单地址", + placeholder: "填写账单地址", + validation: [ + { type: "Required" }, + { type: "MaxLength", args: [":address", 200] }, + ], + }, + }, + ], + filters: [], + "list-layout": { + columns: [{ name: "store_id" }, { name: "orders_amount" }], + filters: [{ name: "订单数量" }], + }, + }, + }; +} diff --git a/tests/widgets/dyform/export.js b/tests/widgets/dyform/export.js new file mode 100644 index 00000000..40f4b658 --- /dev/null +++ b/tests/widgets/dyform/export.js @@ -0,0 +1,152 @@ +/** + * Export Models, APIs, Flows, Tables, Tasks, Schedules, etc. + */ + +/** + * Export Apis + * @param {*} dsl + * @returns + */ +function Apis() { + let apis = {}; + apis[`dyform`] = dyformAPI(); + return apis; +} + +/** + * Export Models + * @param {*} dsl + * @returns + */ +function Models(name, dsl) { + let models = {}; + models[`dyform.${name}`] = dyformModel(name); + return models; +} + +/** + * Export Flows + * @param {*} dsl + * @returns + */ +function Flows(name, dsl) { + return [{}]; +} + +/** + * Export Tables + * @param {*} dsl + * @returns + */ +function Tables(name, dsl) { + let tables = {}; + tables[`dyform.${name}`] = dyformTable(name); + return tables; +} + +/** + * Export Tasks + * @param {*} dsl + * @returns + */ +function Tasks(name, dsl) { + let tasks = {}; + tasks[`dyform.${name}`] = dyformTask(name); + return tasks; +} + +/** + * Export Schedules + * @param {*} dsl + * @returns + */ +function Schedules(name, dsl) { + let schedules = {}; + schedules[`dyform.${name}`] = dyformSchedule(name); + return schedules; +} + +function dyformSchedule(name) { + return { + name: `Schedule ${name}`, + schedule: "*/1 * * * *", + process: `widgets.dyform.Save`, + args: [name], + }; +} + +function dyformTask(name) { + return { + name: `Task ${name}`, + worker_nums: 2, + attempts: 3, + attempt_after: 200, + timeout: 2, + size: 1000, + process: `widgets.dyform.Save`, + args: [name], + }; +} + +function dyformAPI() { + return { + name: `dyform API`, + version: "1.0.0", + description: "dyform APIs", + group: "dyform", + guard: "-", + paths: [ + { + path: "/:name/search", + method: "GET", + process: `widgets.dyform.Save`, + in: ["$param.name", ":query"], + out: { status: 200, type: "application/json" }, + }, + ], + }; +} + +function dyformTable(name) { + return { + name: `dyform_${name}`, + version: "1.0.0", + decription: "云服务库", + bind: { model: `dyform.${name}` }, + apis: {}, + columns: {}, + filters: {}, + list: { + primary: "id", + layout: { columns: [{ name: "ID", width: 6 }], filters: [] }, + actions: {}, + }, + edit: { + primary: "id", + layout: { fieldset: [{ columns: [{ name: "ID", width: 6 }] }] }, + actions: { cancel: {} }, + }, + insert: {}, + view: {}, + }; +} + +function dyformModel(name) { + return { + table: { name: `dyform_${name}` }, + columns: [ + { label: "DYFORM ID", name: "id", type: "ID" }, + { label: "SN", name: "sn", type: "string", length: 20, unique: true }, + { label: "NAME", name: "name", type: "string", length: 200, index: true }, + { label: "SOURCE", name: "source", type: "JSON", nullable: true }, + { + label: "TITLE", + name: "title", + type: "string", + length: 200, + index: true, + }, + ], + indexes: [], + }; +} diff --git a/tests/widgets/dyform/helper.js b/tests/widgets/dyform/helper.js new file mode 100644 index 00000000..7c474b22 --- /dev/null +++ b/tests/widgets/dyform/helper.js @@ -0,0 +1,3 @@ +function Foo(param) { + return `${param} World`; +} diff --git a/tests/widgets/dyform/process.js b/tests/widgets/dyform/process.js new file mode 100644 index 00000000..4f41fb2f --- /dev/null +++ b/tests/widgets/dyform/process.js @@ -0,0 +1,24 @@ +/** + * Export the widget processes + * Export() defined witch function in this file will be registered as YAO PROCESS + * The process name is .. + * The processes can be used in compile.js and export.js DIRECTLY + */ + +/** + * SchemaSave + * Save the schema of dyform + * @name: dyform..Save + * @param {String} instance the instance name + * @param {*} payload + */ +function SchemaSave(instance, payload) { + return { instance: instance, payload: payload }; +} + +/** + * Export processes + */ +function Export() { + return { Save: "SchemaSave" }; +} diff --git a/tests/widgets/dyform/widget.json b/tests/widgets/dyform/widget.json new file mode 100644 index 00000000..e19b1256 --- /dev/null +++ b/tests/widgets/dyform/widget.json @@ -0,0 +1,8 @@ +{ + "label": "Dynamic Form", + "description": "A form widget. users can design forms online", + "version": "0.1.0", + "root": "dyforms", + "extension": ".form.json", + "modules": ["Apis", "Models", "Tables", "Tasks", "Schedules"] +} diff --git a/widget/widget.go b/widget/widget.go new file mode 100644 index 00000000..8bd89b0f --- /dev/null +++ b/widget/widget.go @@ -0,0 +1,107 @@ +package widget + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/yaoapp/gou" + "github.com/yaoapp/gou/widget" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" + "github.com/yaoapp/yao/table" +) + +// Load Widgets +func Load(cfg config.Config) error { + var root = filepath.Join(cfg.Root, "widgets") + return LoadFrom(root) +} + +// LoadFrom widget +func LoadFrom(dir string) error { + + register := moduleRegister() + + if share.DirNotExists(dir) { + return fmt.Errorf("%s does not exists", dir) + } + + paths, err := ioutil.ReadDir(dir) + if err != nil { + return err + } + + for _, path := range paths { + + if !path.IsDir() { + continue + } + + name := path.Name() + if _, err := os.Stat(filepath.Join(dir, name, "widget.json")); errors.Is(err, os.ErrNotExist) { + // path/to/whatever does not exist + continue + } + w, err := gou.LoadWidget(filepath.Join(dir, name), name, register) + if err != nil { + return err + } + + // Load instances + err = w.Load() + if err != nil { + return err + } + } + + return err +} + +func moduleRegister() widget.ModuleRegister { + return widget.ModuleRegister{ + "Apis": func(name string, source []byte) error { + _, err := gou.LoadAPIReturn(string(source), name) + log.Trace("[Widget] Register api %s", name) + if err != nil { + log.Error("[Widget] Register api %s %v", name, err) + } + return err + }, + "Models": func(name string, source []byte) error { + _, err := gou.LoadModelReturn(string(source), name) + log.Trace("[Widget] Register model %s", name) + if err != nil { + log.Error("[Widget] Register model %s %v", name, err) + } + return err + }, + "Tables": func(name string, source []byte) error { + log.Trace("[Widget] Register table %s", name) + _, err := table.LoadTable(string(source), name) + if err != nil { + log.Error("[Widget] Register table %s %v", name, err) + } + return nil + }, + "Tasks": func(name string, source []byte) error { + log.Trace("[Widget] Register task %s", name) + _, err := gou.LoadTask(string(source), name) + if err != nil { + log.Error("[Widget] Register task %s %v", name, err) + } + return nil + }, + "Schedules": func(name string, source []byte) error { + log.Trace("[Widget] Register schedule %s", name) + _, err := gou.LoadSchedule(string(source), name) + if err != nil { + log.Error("[Widget] Register schedule %s %v", name, err) + } + return nil + }, + } +} diff --git a/widget/widget_test.go b/widget/widget_test.go new file mode 100644 index 00000000..7183d019 --- /dev/null +++ b/widget/widget_test.go @@ -0,0 +1,25 @@ +package widget + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/any" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +func TestLoad(t *testing.T) { + share.DBConnect(config.Conf.DB) // 创建数据库连接 + Load(config.Conf) + LoadFrom("not a path") + v, err := gou.NewProcess("widgets.dyform.Save", "pad", "pay").Exec() + if err != nil { + t.Fatal(err) + } + + res := any.Of(v).Map() + assert.Equal(t, "pad", res.Get("instance")) + assert.Equal(t, "pay", res.Get("payload")) +} From 2349d17d99d89c7de4cdbcf877c627ac22f28d8c Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 19 Aug 2022 21:33:48 +0800 Subject: [PATCH 80/96] [add] language pack support for model --- app/app.go | 130 +++---------------------- app/app_test.go | 6 +- lang/lang.go | 36 +++++++ lang/lang_test.go | 16 +++ model/model.go | 8 ++ model/model_test.go | 12 ++- share/types.go | 3 +- tests/langs/en-US.json | 4 - tests/langs/zh-cn/flows/hello.flow.yml | 1 + tests/langs/zh-cn/global.yml | 7 ++ tests/langs/zh-cn/models/demo.mod.yml | 1 + tests/langs/zh-hk/flows/hello.flow.yml | 1 + tests/langs/zh-hk/global.yml | 7 ++ tests/langs/zh-hk/models/demo.mod.yml | 1 + tests/models/demo.mod.json | 35 +++++++ 15 files changed, 143 insertions(+), 125 deletions(-) create mode 100644 lang/lang.go create mode 100644 lang/lang_test.go delete mode 100644 tests/langs/en-US.json create mode 100644 tests/langs/zh-cn/flows/hello.flow.yml create mode 100644 tests/langs/zh-cn/global.yml create mode 100644 tests/langs/zh-cn/models/demo.mod.yml create mode 100644 tests/langs/zh-hk/flows/hello.flow.yml create mode 100644 tests/langs/zh-hk/global.yml create mode 100644 tests/langs/zh-hk/models/demo.mod.yml create mode 100644 tests/models/demo.mod.json diff --git a/app/app.go b/app/app.go index b9c2a83b..03333b8c 100644 --- a/app/app.go +++ b/app/app.go @@ -1,35 +1,32 @@ package app import ( - "fmt" - "os" - "path/filepath" - "strings" - jsoniter "github.com/json-iterator/go" + l "github.com/yaoapp/gou/lang" "github.com/yaoapp/kun/exception" - "github.com/yaoapp/kun/log" "github.com/yaoapp/kun/maps" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/data" + "github.com/yaoapp/yao/lang" "github.com/yaoapp/yao/share" "github.com/yaoapp/yao/xfs" ) -// langs 语言包 -var langs = map[string]map[string]string{} - -// Load 加载应用信息 +// Load Application func Load(cfg config.Config) { - Init(cfg) - LoadInfo(cfg.Root) - LoadLang(cfg) - lang := strings.ToLower(share.App.Lang) + // Load language packs + lang.Load(cfg) + + // Set language pack share.App.L = map[string]string{} - if l, has := langs[lang]; has { - share.App.L = l + if l.Default != nil { + share.App.L = l.Default.Global } + + // Load Info + LoadInfo(cfg.Root) + } // L 语言包 @@ -40,49 +37,6 @@ func L(word string) string { return word } -// Init 应用初始化 -func Init(cfg config.Config) { - - // // UI文件目录 - // if _, err := os.Stat(cfg.RootUI); os.IsNotExist(err) { - // err := os.MkdirAll(cfg.RootUI, os.ModePerm) - // if err != nil { - // xlog.Printf("创建目录失败(%s) %s", cfg.RootUI, err) - // os.Exit(1) - // } - - // content, err := data.Asset("xiang/data/index.html") - // if err != nil { - // xlog.Printf("读取文件失败(%s) %s", cfg.RootUI, err) - // os.Exit(1) - // } - - // err = ioutil.WriteFile(filepath.Join(cfg.RootUI, "/index.html"), content, os.ModePerm) - // if err != nil { - // xlog.Printf("复制默认文件失败(%s) %s", cfg.RootUI, err) - // os.Exit(1) - // } - // } - - // // 数据库目录 - // if _, err := os.Stat(cfg.RootDB); os.IsNotExist(err) { - // err := os.MkdirAll(cfg.RootDB, os.ModePerm) - // if err != nil { - // xlog.Printf("创建目录失败(%s) %s", cfg.RootDB, err) - // os.Exit(1) - // } - // } - - // // 文件数据目录 - // if _, err := os.Stat(cfg.RootData); os.IsNotExist(err) { - // err := os.MkdirAll(cfg.RootData, os.ModePerm) - // if err != nil { - // xlog.Printf("创建目录失败(%s) %s", cfg.RootData, err) - // os.Exit(1) - // } - // } -} - // LoadInfo 应用信息 func LoadInfo(root string) { info := defaultInfo() @@ -106,71 +60,15 @@ func LoadInfo(root string) { info.Icons.Set("png", xfs.Encode(fs.MustReadFile("/yao/icons/icon.png"))) } + info.L = share.App.L share.App = info } -// LoadLang 加载语言包 -func LoadLang(cfg config.Config) error { - - var defaults = []share.Script{} - if os.Getenv("YAO_DEV") != "" { - defaults = share.GetFilesFS(filepath.Join(os.Getenv("YAO_DEV"), "yao"), ".json") - } else { - defaults = share.GetFilesBin("yao", ".json") - } - - for _, lang := range defaults { - if lang.Type != "langs" { - continue - } - content := lang.Content - name := strings.ToLower(lang.Name) // 这个读取函数需要优化 - lang := map[string]string{} - err := jsoniter.Unmarshal(content, &lang) - if err != nil { - log.With(log.F{"name": name}).Error(err.Error()) - } - langs[name] = lang - } - - if share.BUILDIN { - return LoadLangBuildIn("langs") - } - return LoadLangFrom(filepath.Join(cfg.Root, "langs")) -} - // LoadLangBuildIn 从制品中读取 func LoadLangBuildIn(dir string) error { return nil } -// LoadLangFrom 从特定目录加载 -func LoadLangFrom(dir string) error { - - if share.DirNotExists(dir) { - return fmt.Errorf("%s does not exists", dir) - } - - err := share.Walk(dir, ".json", func(root, filename string) { - name := strings.ToLower(share.SpecName(root, filename)) - content := share.ReadFile(filename) - lang := map[string]string{} - err := jsoniter.Unmarshal(content, &lang) - if err != nil { - log.With(log.F{"root": root, "file": filename}).Error(err.Error()) - } - if _, has := langs[name]; !has { - langs[name] = map[string]string{} - } - for src, dst := range lang { - langs[name][src] = dst - } - }) - - return err - -} - // defaultInfo 读取默认应用信息 func defaultInfo() share.AppInfo { info := share.AppInfo{ diff --git a/app/app_test.go b/app/app_test.go index 05305342..32ea49bc 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -1,6 +1,7 @@ package app import ( + "os" "testing" "github.com/stretchr/testify/assert" @@ -9,7 +10,8 @@ import ( ) func TestLoad(t *testing.T) { + os.Setenv("YAO_LANG", "zh-cn") Load(config.Conf) - assert.Equal(t, "Yao", share.App.L["Yao"]) - assert.Equal(t, "Xiang", share.App.L["象传"]) + assert.Equal(t, "YAO", share.App.L["Yao"]) + assert.Equal(t, "象传", share.App.L["Xiang"]) } diff --git a/lang/lang.go b/lang/lang.go new file mode 100644 index 00000000..69534959 --- /dev/null +++ b/lang/lang.go @@ -0,0 +1,36 @@ +package lang + +import ( + "os" + "path/filepath" + + "github.com/yaoapp/gou/lang" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" +) + +func init() { + lang.RegisterWidget("tables", "table") + lang.RegisterWidget("charts", "chart") + lang.RegisterWidget("kanban", "kanban") +} + +// Load language packs +func Load(cfg config.Config) error { + root := filepath.Join(cfg.Root, "langs") + err := lang.Load(root) + if err != nil { + return err + } + + name := os.Getenv("YAO_LANG") + if name != "" { + if _, has := lang.Dicts[name]; !has { + log.Error("The language pack %s does not found", name) + return nil + } + lang.Pick(name).AsDefault() + } + + return nil +} diff --git a/lang/lang_test.go b/lang/lang_test.go new file mode 100644 index 00000000..68763869 --- /dev/null +++ b/lang/lang_test.go @@ -0,0 +1,16 @@ +package lang + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou/lang" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +func TestLoad(t *testing.T) { + share.DBConnect(config.Conf.DB) + Load(config.Conf) + assert.Len(t, lang.Dicts, 2) +} diff --git a/model/model.go b/model/model.go index f4fa6f56..e71e8425 100644 --- a/model/model.go +++ b/model/model.go @@ -5,6 +5,7 @@ import ( "path/filepath" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/lang" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/share" @@ -31,7 +32,14 @@ func LoadFrom(dir string, prefix string) error { _, err := gou.LoadModelReturn(string(content), name) if err != nil { log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + return } + + // Apply the language pack + if lang.Default != nil { + lang.Default.Apply(gou.Models[name]) + } + }) return err diff --git a/model/model_test.go b/model/model_test.go index 52a485b3..02df055f 100644 --- a/model/model_test.go +++ b/model/model_test.go @@ -1,17 +1,23 @@ package model import ( + "os" "testing" "github.com/stretchr/testify/assert" "github.com/yaoapp/gou" "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/lang" "github.com/yaoapp/yao/share" ) func TestLoad(t *testing.T) { + + os.Setenv("YAO_LANG", "zh-hk") + lang.Load(config.Conf) share.DBConnect(config.Conf.DB) + gou.Models = make(map[string]*gou.Model) Load(config.Conf) LoadFrom("not a path", "404.") @@ -23,5 +29,9 @@ func check(t *testing.T) { for key := range gou.Models { keys = append(keys, key) } - assert.Equal(t, 10, len(keys)) + assert.Equal(t, 11, len(keys)) + + demo := gou.Select("demo") + assert.Equal(t, demo.MetaData.Name, "演示") + assert.Equal(t, demo.Columns["action"].Label, "動作") } diff --git a/share/types.go b/share/types.go index 60ec16a9..705bf6d4 100644 --- a/share/types.go +++ b/share/types.go @@ -72,8 +72,7 @@ type Page struct { // AppInfo 应用信息 type AppInfo struct { Name string `json:"name,omitempty"` - Lang string `json:"lang,omitempty"` - L map[string]string `json:"-"` // 应用的语言包 + L map[string]string `json:"-"` Short string `json:"short,omitempty"` Version string `json:"version,omitempty"` Description string `json:"description,omitempty"` diff --git a/tests/langs/en-US.json b/tests/langs/en-US.json deleted file mode 100644 index 54d43606..00000000 --- a/tests/langs/en-US.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Yao": "Yao", - "象传应用引擎": "Yao App Engine Over load" -} diff --git a/tests/langs/zh-cn/flows/hello.flow.yml b/tests/langs/zh-cn/flows/hello.flow.yml new file mode 100644 index 00000000..e15a69fa --- /dev/null +++ b/tests/langs/zh-cn/flows/hello.flow.yml @@ -0,0 +1 @@ +Latest: 最新信息 diff --git a/tests/langs/zh-cn/global.yml b/tests/langs/zh-cn/global.yml new file mode 100644 index 00000000..61133bfe --- /dev/null +++ b/tests/langs/zh-cn/global.yml @@ -0,0 +1,7 @@ +Demo: 演示 +Phone: 电话号码 +ZipCode: 邮政编码 +Uptime: 更新时间 +Action: 动作 +Yao: YAO +Xiang: 象传 diff --git a/tests/langs/zh-cn/models/demo.mod.yml b/tests/langs/zh-cn/models/demo.mod.yml new file mode 100644 index 00000000..8eb7cc67 --- /dev/null +++ b/tests/langs/zh-cn/models/demo.mod.yml @@ -0,0 +1 @@ +SN: 编码 diff --git a/tests/langs/zh-hk/flows/hello.flow.yml b/tests/langs/zh-hk/flows/hello.flow.yml new file mode 100644 index 00000000..e15a69fa --- /dev/null +++ b/tests/langs/zh-hk/flows/hello.flow.yml @@ -0,0 +1 @@ +Latest: 最新信息 diff --git a/tests/langs/zh-hk/global.yml b/tests/langs/zh-hk/global.yml new file mode 100644 index 00000000..c820c37e --- /dev/null +++ b/tests/langs/zh-hk/global.yml @@ -0,0 +1,7 @@ +Demo: 演示 +Phone: 電話號碼 +ZipCode: 郵政編碼 +Uptime: 更新時間 +Action: 動作 +Yao: YAO +Xiang: 像傳 diff --git a/tests/langs/zh-hk/models/demo.mod.yml b/tests/langs/zh-hk/models/demo.mod.yml new file mode 100644 index 00000000..30134633 --- /dev/null +++ b/tests/langs/zh-hk/models/demo.mod.yml @@ -0,0 +1 @@ +SN: 編碼 diff --git a/tests/models/demo.mod.json b/tests/models/demo.mod.json new file mode 100644 index 00000000..e827cdcd --- /dev/null +++ b/tests/models/demo.mod.json @@ -0,0 +1,35 @@ +{ + "name": "::Demo", + "connector": "trace", + "table": { "name": "user_trace", "comment": "::Demo" }, + "columns": [ + { "label": "ID", "name": "id", "type": "ID" }, + { + "label": "::SN", + "name": "user_sn", + "type": "string", + "length": 42, + "index": true + }, + { + "label": "::Uptime", + "name": "uptime", + "type": "datetime", + "index": true + }, + { + "label": "::Action", + "name": "action", + "type": "enum", + "option": ["进入", "离开"], + "index": true + } + ], + "indexes": [ + { + "name": "user_sn_action_unique", + "type": "unique", + "columns": ["user_sn", "action"] + } + ] +} From 30346b1f7b0cad0338ec04be52b0be87b9dcc0c5 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 19 Aug 2022 23:46:57 +0800 Subject: [PATCH 81/96] [add] language pack support for table --- table/lang.go | 98 +++++++++++++++++++++ table/table.go | 14 +++ table/table_test.go | 17 +++- table/validate.go | 132 ++++++++++++++++++++++++++++- tests/langs/zh-cn/tables/demo.yml | 10 +++ tests/langs/zh-hk/tables/demo.yml | 10 +++ tests/tables/assign.tab.json | 2 +- tests/tables/demo.tab.json | 85 +++++++++++++++++++ tests/tables/foo/bar.tab.json | 66 ++------------- tests/tables/hooks/find.tab.json | 4 +- tests/tables/hooks/save.tab.json | 4 +- tests/tables/hooks/search.tab.json | 2 +- tests/tables/hooks/select.tab.json | 4 +- 13 files changed, 375 insertions(+), 73 deletions(-) create mode 100644 table/lang.go create mode 100644 tests/langs/zh-cn/tables/demo.yml create mode 100644 tests/langs/zh-hk/tables/demo.yml create mode 100644 tests/tables/demo.tab.json diff --git a/table/lang.go b/table/lang.go new file mode 100644 index 00000000..4b23f071 --- /dev/null +++ b/table/lang.go @@ -0,0 +1,98 @@ +package table + +// Lang for applying a language pack +func (table *Table) Lang(trans func(widget string, inst string, value *string) bool) { + inst := table.Table + widget := "table" + + trans(widget, inst, &table.Name) + trans(widget, inst, &table.Decription) + + // Columns + for name, column := range table.Columns { + new := name + trans(widget, inst, &new) + trans(widget, inst, &column.Label) + + // Props + transMap(widget, inst, column.Edit.Props, trans) + transMap(widget, inst, column.View.Props, trans) + transMap(widget, inst, column.Form.Props, trans) + + delete(table.Columns, name) + table.Columns[new] = column + } + + // Filters + for name, filter := range table.Filters { + new := name + trans(widget, inst, &new) + trans(widget, inst, &filter.Label) + delete(table.Filters, name) + + // Props + transMap(widget, inst, filter.Input.Props, trans) + table.Filters[new] = filter + } + + // List + transMap(widget, inst, table.List.Layout, trans) + transMap(widget, inst, table.List.Option, trans) + + // Edit + transMap(widget, inst, table.Edit.Layout, trans) + transMap(widget, inst, table.Edit.Option, trans) +} + +func transMap(widget string, inst string, values map[string]interface{}, trans func(widget string, inst string, value *string) bool) { + for key, value := range values { + + switch value.(type) { + + case string: + v := value.(string) + trans(widget, inst, &v) + values[key] = v + break + + case []interface{}: + v := value.([]interface{}) + transArr(widget, inst, v, trans) + values[key] = v + break + + case map[string]interface{}: + v := value.(map[string]interface{}) + transMap(widget, inst, v, trans) + values[key] = v + break + } + + } +} + +func transArr(widget string, inst string, values []interface{}, trans func(widget string, inst string, value *string) bool) { + for key, value := range values { + switch value.(type) { + + case string: + v := value.(string) + trans(widget, inst, &v) + values[key] = v + break + + case []interface{}: + v := value.([]interface{}) + transArr(widget, inst, v, trans) + values[key] = v + break + + case map[string]interface{}: + v := value.(map[string]interface{}) + transMap(widget, inst, v, trans) + values[key] = v + break + } + + } +} diff --git a/table/table.go b/table/table.go index 5cf15a80..765bbdd7 100644 --- a/table/table.go +++ b/table/table.go @@ -12,6 +12,7 @@ import ( "github.com/xuri/excelize/v2" "github.com/yaoapp/gou" "github.com/yaoapp/gou/helper" + "github.com/yaoapp/gou/lang" "github.com/yaoapp/kun/any" "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/log" @@ -172,7 +173,20 @@ func LoadTable(source string, name string) (*Table, error) { table.loadColumns() table.loadFilters() table.loadAPIs() + + err = table.Validate() + if err != nil { + log.Error("[Table] %s is not valid: %s ", table.Table, err.Error()) + return nil, err + } + Tables[name] = &table + + // Apply a language pack + if lang.Default != nil { + lang.Default.Apply(Tables[name]) + } + return Tables[name], nil } diff --git a/table/table_test.go b/table/table_test.go index c3e5f245..c57ec2a9 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -1,16 +1,22 @@ package table import ( + "os" "testing" "github.com/stretchr/testify/assert" "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/lang" "github.com/yaoapp/yao/model" "github.com/yaoapp/yao/share" ) func TestLoad(t *testing.T) { + + os.Setenv("YAO_LANG", "zh-hk") + lang.Load(config.Conf) + share.DBConnect(config.Conf.DB) share.Load(config.Conf) model.Load(config.Conf) @@ -48,5 +54,14 @@ func check(t *testing.T) { for key := range Tables { keys = append(keys, key) } - assert.Equal(t, 9, len(keys)) + assert.Equal(t, 10, len(keys)) + + demo := Select("demo") + assert.NotNil(t, demo.Columns["類型"]) + assert.NotNil(t, demo.Columns["類型"].Edit.Props["options"]) + + options := demo.Columns["類型"].Edit.Props["options"].([]interface{}) + opt1 := options[0].(map[string]interface{}) + assert.Equal(t, "貓", opt1["label"]) + } diff --git a/table/validate.go b/table/validate.go index 01df3881..52db7db6 100644 --- a/table/validate.go +++ b/table/validate.go @@ -1,6 +1,132 @@ package table +import "fmt" + // Validate 校验表格格式 -// func (table Table) Validate() error { -// return nil -// } +func (table Table) Validate() error { + err := table.validateList() + if err != nil { + return err + } + + err = table.validateEdit() + if err != nil { + return err + } + + return nil +} + +func (table Table) validateEdit() error { + if table.Edit.Layout == nil { + return nil + } + + fieldset, ok := table.Edit.Layout["fieldset"].([]interface{}) + if !ok { + return fmt.Errorf("edit.layout.columns is required") + } + + for idx, set := range fieldset { + set, ok := set.(map[string]interface{}) + if !ok { + return fmt.Errorf("edit.layout.fieldset.%d format error", idx) + } + + columns, ok := set["columns"].([]interface{}) + if !ok { + return fmt.Errorf("edit.layout.fieldset.%d.columns format error", idx) + } + + for cidx, column := range columns { + col, ismap := column.(map[string]interface{}) + name, isstr := column.(string) + if !ismap && !isstr { + return fmt.Errorf("edit.layout.fieldset.%d.columns.%d format error", idx, cidx) + } + + if ismap { + v, has := col["name"] + if !has { + return fmt.Errorf("edit.layout.fieldset.%d.columns.%d.name is required", idx, cidx) + } + namestr, ok := v.(string) + if !ok { + return fmt.Errorf("edit.layout.fieldset.%d.columns.%d.name format error", idx, cidx) + } + name = namestr + } + + if _, has := table.Columns[name]; !has { + return fmt.Errorf("edit.layout.fieldset.%d.columns.%d.name %s is not found in columns", idx, cidx, name) + } + } + + } + + return nil +} + +func (table Table) validateList() error { + + if table.List.Layout == nil { + return nil + } + + // table.List.Layout + columns, ok := table.List.Layout["columns"].([]interface{}) + if !ok { + return fmt.Errorf("list.layout.columns is required") + } + + for idx, column := range columns { + + col, ismap := column.(map[string]interface{}) + name, isstr := column.(string) + if !ismap && !isstr { + return fmt.Errorf("list.layout.columns.%d format error", idx) + } + + if ismap { + v, has := col["name"] + if !has { + return fmt.Errorf("list.layout.columns.%d.name is required", idx) + } + namestr, ok := v.(string) + if !ok { + return fmt.Errorf("list.layout.columns.%d.name format error", idx) + } + name = namestr + } + + if _, has := table.Columns[name]; !has { + return fmt.Errorf("list.layout.columns.%d.name %s is not found in columns", idx, name) + } + } + + filters, ok := table.List.Layout["filters"].([]interface{}) + if ok { + for idx, fliter := range filters { + fli, ok := fliter.(map[string]interface{}) + if !ok { + return fmt.Errorf("list.layout.fliters.%d format error", idx) + } + + name, has := fli["name"] + if !has { + return fmt.Errorf("list.layout.fliters.%d.name is required", idx) + } + + namestr, ok := name.(string) + if !ok { + return fmt.Errorf("list.layout.fliters.%d.name format error", idx) + } + + if _, has := table.Filters[namestr]; !has { + return fmt.Errorf("list.layout.fliters.%d.name %s is not found in fliters", idx, namestr) + } + } + } + + return nil +} diff --git a/tests/langs/zh-cn/tables/demo.yml b/tests/langs/zh-cn/tables/demo.yml new file mode 100644 index 00000000..17bcd44b --- /dev/null +++ b/tests/langs/zh-cn/tables/demo.yml @@ -0,0 +1,10 @@ +ID: 序号 +SN: 编码 +Name: 姓名 +Kind: 类型 +Cat: 猫 +Dog: 狗 +Description: 介绍 +Keywords: 关键词 +Please input the SN: 请输入编码 +type the keywords...: 关键词... diff --git a/tests/langs/zh-hk/tables/demo.yml b/tests/langs/zh-hk/tables/demo.yml new file mode 100644 index 00000000..42d52b75 --- /dev/null +++ b/tests/langs/zh-hk/tables/demo.yml @@ -0,0 +1,10 @@ +ID: 序號 +SN: 編碼 +Name: 姓名 +Kind: 類型 +Description: 介绍 +Keywords: 關鍵詞 +Cat: 貓 +Dog: 狗 +Please input the SN: 請輸入編碼 +type the keywords...: 關鍵詞... diff --git a/tests/tables/assign.tab.json b/tests/tables/assign.tab.json index db60aa71..093e85f6 100644 --- a/tests/tables/assign.tab.json +++ b/tests/tables/assign.tab.json @@ -8,7 +8,7 @@ "layout": { "fieldset": [ { - "columns": [{ "name": "商务负责人", "width": 24 }] + "columns": [{ "name": "name", "width": 24 }] } ] } diff --git a/tests/tables/demo.tab.json b/tests/tables/demo.tab.json new file mode 100644 index 00000000..11d38e06 --- /dev/null +++ b/tests/tables/demo.tab.json @@ -0,0 +1,85 @@ +{ + "name": "::Demo", + "version": "1.0.0", + "decription": "::Demo", + "bind": { "model": "demo" }, + "apis": {}, + "columns": { + "::ID": { + "label": "::ID", + "view": { "type": "label", "props": { "value": ":id" } } + }, + "::SN": { + "label": "::SN", + "view": { "type": "label", "props": { "value": ":sn" } }, + "edit": { + "type": "input", + "props": { "value": ":sn", "placeholder": "::Please input the SN" } + } + }, + "::Name": { + "label": "::Name", + "view": { "type": "label", "props": { "value": ":name" } }, + "edit": { "type": "input", "props": { "value": ":name" } } + }, + "::Kind": { + "label": "::Kind", + "view": { "type": "label", "props": { "value": ":kind" } }, + "edit": { + "type": "select", + "props": { + "value": ":kind", + "options": [ + { "label": "::Cat", "value": "cat" }, + { "label": "::Dog", "value": "dog" } + ] + } + } + }, + "::Description": { + "label": "::Description", + "view": { "type": "label", "props": { "value": ":desc" } }, + "edit": { "type": "textArea", "props": { "value": ":desc", "rows": 4 } } + } + }, + "filters": { + "::Keywords": { + "label": "::Keywords", + "bind": "where.name.match", + "input": { + "type": "input", + "props": { "placeholder": "::type the keywords..." } + } + } + }, + "list": { + "primary": "id", + "layout": { + "columns": [ + { "name": "::ID", "width": 80 }, + { "name": "::SN", "width": 100 }, + { "name": "::Name", "width": 200 }, + { "name": "::Kind" } + ], + "filters": [{ "name": "::Keywords" }] + }, + "actions": { "pagination": { "props": { "showTotal": true } } }, + "option": { "operation": { "unfold": true } } + }, + "edit": { + "primary": "id", + "layout": { + "fieldset": [ + { + "columns": [ + { "name": "::SN", "width": 8 }, + { "name": "::Name", "width": 8 }, + { "name": "::Kind", "width": 8 }, + { "name": "::Description", "width": 24 } + ] + } + ] + }, + "actions": { "cancel": {}, "save": {}, "delete": {} } + } +} diff --git a/tests/tables/foo/bar.tab.json b/tests/tables/foo/bar.tab.json index 0cd473dc..fb76ec06 100644 --- a/tests/tables/foo/bar.tab.json +++ b/tests/tables/foo/bar.tab.json @@ -11,47 +11,13 @@ "list": { "primary": "id", "layout": { - "columns": [ - { - "name": "名称", - "width": "240px" - }, - { - "name": "父节点" - }, - { - "name": "图标" - }, - { - "name": "状态" - }, - { - "name": "路由" - }, - { - "name": "排列" - }, - { - "name": "block布局" - }, - { - "name": "显示" - } - ], - "filters": [ - { - "name": "名称", - "width": 6 - } - ] + "columns": [{ "name": "name", "width": "240px" }], + "filters": [{ "name": "name", "width": 6 }] }, "actions": { "create": { "type": "button", - "props": { - "label": "新建菜单", - "icon": "fas fa-plus" - } + "props": { "label": "新建菜单", "icon": "fas fa-plus" } }, "view": {}, "edit": {}, @@ -75,30 +41,8 @@ "title": "基础信息", "description": "", "columns": [ - { - "name": "名称", - "width": 6 - }, - { - "name": "图标", - "width": 6 - }, - { - "name": "路由", - "width": 6 - }, - { - "name": "排列", - "width": 6 - }, - { - "name": "block布局", - "width": 6 - }, - { - "name": "显示", - "width": 6 - } + { "name": "name", "width": 6 }, + { "name": "id", "width": 6 } ] } ] diff --git a/tests/tables/hooks/find.tab.json b/tests/tables/hooks/find.tab.json index b0016c95..eb5c1233 100644 --- a/tests/tables/hooks/find.tab.json +++ b/tests/tables/hooks/find.tab.json @@ -13,7 +13,7 @@ "list": { "primary": "id", "layout": { - "columns": [{ "name": "ID", "width": 6 }], + "columns": [{ "name": "id", "width": 6 }], "filters": [] }, "actions": {} @@ -21,7 +21,7 @@ "edit": { "primary": "id", "layout": { - "fieldset": [{ "columns": [{ "name": "ID", "width": 6 }] }] + "fieldset": [{ "columns": [{ "name": "id", "width": 6 }] }] }, "actions": { "cancel": {} } }, diff --git a/tests/tables/hooks/save.tab.json b/tests/tables/hooks/save.tab.json index d0e1c859..455093aa 100644 --- a/tests/tables/hooks/save.tab.json +++ b/tests/tables/hooks/save.tab.json @@ -13,7 +13,7 @@ "list": { "primary": "id", "layout": { - "columns": [{ "name": "ID", "width": 6 }], + "columns": [{ "name": "id", "width": 6 }], "filters": [] }, "actions": {} @@ -21,7 +21,7 @@ "edit": { "primary": "id", "layout": { - "fieldset": [{ "columns": [{ "name": "ID", "width": 6 }] }] + "fieldset": [{ "columns": [{ "name": "id", "width": 6 }] }] }, "actions": { "cancel": {} } }, diff --git a/tests/tables/hooks/search.tab.json b/tests/tables/hooks/search.tab.json index 46f6a8b8..926d544f 100644 --- a/tests/tables/hooks/search.tab.json +++ b/tests/tables/hooks/search.tab.json @@ -30,7 +30,7 @@ "edit": { "primary": "id", "layout": { - "fieldset": [{ "columns": [{ "name": "ID", "width": 6 }] }] + "fieldset": [{ "columns": [{ "name": "id", "width": 6 }] }] }, "actions": { "cancel": {} } }, diff --git a/tests/tables/hooks/select.tab.json b/tests/tables/hooks/select.tab.json index ea4e031f..1c9f6dc1 100644 --- a/tests/tables/hooks/select.tab.json +++ b/tests/tables/hooks/select.tab.json @@ -13,7 +13,7 @@ "list": { "primary": "id", "layout": { - "columns": [{ "name": "ID", "width": 6 }], + "columns": [{ "name": "id", "width": 6 }], "filters": [] }, "actions": {} @@ -21,7 +21,7 @@ "edit": { "primary": "id", "layout": { - "fieldset": [{ "columns": [{ "name": "ID", "width": 6 }] }] + "fieldset": [{ "columns": [{ "name": "id", "width": 6 }] }] }, "actions": { "cancel": {} } }, From 60828dc2ba2f52470f0652bae2d2a6250a9c81e3 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 19 Aug 2022 23:52:10 +0800 Subject: [PATCH 82/96] [fix] misspelling --- table/validate.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/table/validate.go b/table/validate.go index 52db7db6..c269a168 100644 --- a/table/validate.go +++ b/table/validate.go @@ -109,21 +109,21 @@ func (table Table) validateList() error { for idx, fliter := range filters { fli, ok := fliter.(map[string]interface{}) if !ok { - return fmt.Errorf("list.layout.fliters.%d format error", idx) + return fmt.Errorf("list.layout.filters.%d format error", idx) } name, has := fli["name"] if !has { - return fmt.Errorf("list.layout.fliters.%d.name is required", idx) + return fmt.Errorf("list.layout.filters.%d.name is required", idx) } namestr, ok := name.(string) if !ok { - return fmt.Errorf("list.layout.fliters.%d.name format error", idx) + return fmt.Errorf("list.layout.filters.%d.name format error", idx) } if _, has := table.Filters[namestr]; !has { - return fmt.Errorf("list.layout.fliters.%d.name %s is not found in fliters", idx, namestr) + return fmt.Errorf("list.layout.filters.%d.name %s is not found in filters", idx, namestr) } } } From 22d93f10d9e38cbd7212d22a32204db3f3fed877 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 19 Aug 2022 23:53:41 +0800 Subject: [PATCH 83/96] [fix] misspelling --- table/validate.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/table/validate.go b/table/validate.go index c269a168..6f5be474 100644 --- a/table/validate.go +++ b/table/validate.go @@ -106,8 +106,8 @@ func (table Table) validateList() error { filters, ok := table.List.Layout["filters"].([]interface{}) if ok { - for idx, fliter := range filters { - fli, ok := fliter.(map[string]interface{}) + for idx, filter := range filters { + fli, ok := filter.(map[string]interface{}) if !ok { return fmt.Errorf("list.layout.filters.%d format error", idx) } From 850e4dacc55c36f24843e5b132de524e3e9b06fc Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 20 Aug 2022 16:22:54 +0800 Subject: [PATCH 84/96] [add] language pack support for chart --- chart/chart.go | 7 ++ chart/chart_test.go | 16 +++- chart/lang.go | 112 ++++++++++++++++++++++++ tests/charts/lang.chart.json | 100 +++++++++++++++++++++ tests/charts/service/compare.chart.json | 2 +- tests/langs/zh-hk/charts/lang.yml | 10 +++ 6 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 chart/lang.go create mode 100644 tests/charts/lang.chart.json create mode 100644 tests/langs/zh-hk/charts/lang.yml diff --git a/chart/chart.go b/chart/chart.go index 4553607b..e0714152 100644 --- a/chart/chart.go +++ b/chart/chart.go @@ -6,6 +6,7 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/lang" "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" @@ -75,6 +76,12 @@ func LoadChart(source []byte, name string) (*Chart, error) { chart.Prepare() chart.SetupAPIs() Charts[name] = chart + + // Apply a language pack + if lang.Default != nil { + lang.Default.Apply(Charts[name]) + } + return chart, nil } diff --git a/chart/chart_test.go b/chart/chart_test.go index 6fcd1b15..afed79f0 100644 --- a/chart/chart_test.go +++ b/chart/chart_test.go @@ -1,16 +1,22 @@ package chart import ( + "os" "testing" "github.com/stretchr/testify/assert" "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/lang" "github.com/yaoapp/yao/model" "github.com/yaoapp/yao/query" "github.com/yaoapp/yao/share" ) func TestLoad(t *testing.T) { + + os.Setenv("YAO_LANG", "zh-hk") + lang.Load(config.Conf) + share.DBConnect(config.Conf.DB) model.Load(config.Conf) query.Load(config.Conf) @@ -25,5 +31,13 @@ func check(t *testing.T) { for key := range Charts { keys = append(keys, key) } - assert.Equal(t, 2, len(keys)) + assert.Equal(t, 3, len(keys)) + + lang := Charts["lang"] + output := lang.Output.(map[string]interface{}) + assert.Equal(t, "{{$in}}", output["參數"]) + + filters := lang.Page.Layout["filters"].([]interface{}) + begin := filters[0].(map[string]interface{}) + assert.Equal(t, "開始時間", begin["name"]) } diff --git a/chart/lang.go b/chart/lang.go new file mode 100644 index 00000000..68c4cd30 --- /dev/null +++ b/chart/lang.go @@ -0,0 +1,112 @@ +package chart + +// Lang for applying a language pack +func (chart *Chart) Lang(trans func(widget string, inst string, value *string) bool) { + inst := chart.Flow.Name + widget := "chart" + + trans(widget, inst, &chart.Name) + trans(widget, inst, &chart.Label) + trans(widget, inst, &chart.Description) + trans(widget, inst, &chart.Page.Primary) + transMap(widget, inst, chart.Page.Layout, trans) + chart.Output = transAny(widget, inst, chart.Output, trans) +} + +func transAny(widget string, inst string, input interface{}, trans func(widget string, inst string, value *string) bool) interface{} { + switch input.(type) { + case []interface{}: + values := input.([]interface{}) + transArr(widget, inst, values, trans) + input = values + break + + case map[string]interface{}: + values := input.(map[string]interface{}) + for name, value := range values { + new := name + newValue := value + + switch value.(type) { + case string: + val := value.(string) + trans(widget, inst, &val) + newValue = val + break + + case []interface{}: + vals := value.([]interface{}) + transArr(widget, inst, vals, trans) + newValue = vals + break + + case map[string]interface{}: + vals := value.(map[string]interface{}) + transMap(widget, inst, vals, trans) + newValue = vals + break + } + + trans(widget, inst, &new) + delete(values, name) + values[new] = newValue + } + input = values + break + } + + return input +} + +func transMap(widget string, inst string, values map[string]interface{}, trans func(widget string, inst string, value *string) bool) { + for key, value := range values { + + switch value.(type) { + + case string: + v := value.(string) + trans(widget, inst, &v) + values[key] = v + break + + case []interface{}: + v := value.([]interface{}) + transArr(widget, inst, v, trans) + values[key] = v + break + + case map[string]interface{}: + v := value.(map[string]interface{}) + transMap(widget, inst, v, trans) + values[key] = v + break + } + + } +} + +func transArr(widget string, inst string, values []interface{}, trans func(widget string, inst string, value *string) bool) { + for key, value := range values { + switch value.(type) { + + case string: + v := value.(string) + trans(widget, inst, &v) + values[key] = v + break + + case []interface{}: + v := value.([]interface{}) + transArr(widget, inst, v, trans) + values[key] = v + break + + case map[string]interface{}: + v := value.(map[string]interface{}) + transMap(widget, inst, v, trans) + values[key] = v + break + } + + } +} diff --git a/tests/charts/lang.chart.json b/tests/charts/lang.chart.json new file mode 100644 index 00000000..f2553516 --- /dev/null +++ b/tests/charts/lang.chart.json @@ -0,0 +1,100 @@ +{ + "label": "::Demo", + "version": "1.0.0", + "description": "::Demo", + "nodes": [ + { + "name": "行业", + "engine": "xiang", + "query": { + "select": [ + "city", + ":COUNT(id) as 数量", + "industries[*](string 50) as industry" + ], + "from": "$service", + "wheres": [ + { "field": "created_at", ">": "?:$from.0" }, + { "field": "created_at", "<": "?:$to" } + ], + "orders": "数量 desc", + "limit": 100 + } + }, + { + "name": "计费", + "engine": "xiang", + "query": { + "select": ["city", ":COUNT(id) as 数量", "price_options[*] as option"], + "from": "$service", + "wheres": [ + { "field": "created_at", ">": "?:$from.0" }, + { "field": "created_at", "<": "?:$to" } + ], + "orders": "数量 desc", + "limit": 100 + } + }, + { + "name": "合并结果", + "process": "xiang.helper.ArrayPluck", + "args": [ + ["城市", "行业", "计费"], + { + "行业": { "key": "city", "value": "数量", "items": "{{$res.行业}}" }, + "计费": { "key": "city", "value": "数量", "items": "{{$res.计费}}" } + } + ] + } + ], + "output": { + "::Merge": "{{$res.合并结果}}", + "::Billing": "{{$res.计费}}", + "::Industry": "{{$res.行业}}", + "::Parameter": "{{$in}}" + }, + "apis": { + "data": { + "disable": false, + "guard": "-", + "default": [{ "from": "1980-01-02", "to": "2050-12-31" }] + }, + "setting": { "disable": false, "guard": "-" } + }, + "filters": { + "::Start": { + "label": "::Start", + "bind": "from", + "input": { + "type": "date", + "props": { + "placeholder": "::Please select a start time" + } + } + }, + "::End": { + "label": "::End", + "bind": "to", + "input": { + "type": "date", + "props": { + "placeholder": "::Please select an end time" + } + } + } + }, + "page": { + "primary": "::City", + "layout": { + "filters": [ + { "name": "::Start", "width": 6 }, + { "name": "::End", "width": 6 } + ], + "charts": [ + { "type": "line", "props": {} }, + { "type": "bar", "props": {} } + ] + }, + "actions": {} + } +} diff --git a/tests/charts/service/compare.chart.json b/tests/charts/service/compare.chart.json index 96557046..0c66d6fb 100644 --- a/tests/charts/service/compare.chart.json +++ b/tests/charts/service/compare.chart.json @@ -7,7 +7,7 @@ "name": "行业", "engine": "xiang", "query": { - "debug": true, + "debug": false, "select": [ "city", ":COUNT(id) as 数量", diff --git a/tests/langs/zh-hk/charts/lang.yml b/tests/langs/zh-hk/charts/lang.yml new file mode 100644 index 00000000..0f0a4714 --- /dev/null +++ b/tests/langs/zh-hk/charts/lang.yml @@ -0,0 +1,10 @@ +Start: 開始時間 +End: 結束時間 +City: 城市 +Please select a start time: 請選擇開始時間 +Please select an end time: 請選擇結束時間 +Industry: 行業 +Billing: 計費 +Merge Reulst: 合併結果 +Parameter: 參數 +Merge: 合併 From 6283b6e34c7af1ef30e96e64d88e441421ec9c94 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 20 Aug 2022 16:40:27 +0800 Subject: [PATCH 85/96] [add] language pack support for page --- chart/chart_test.go | 2 + chart/lang.go | 13 +++ lang/lang.go | 4 +- page/lang.go | 125 ++++++++++++++++++++++++++ page/page.go | 13 ++- page/page_test.go | 18 +++- page/process_test.go | 3 - tests/langs/zh-hk/pages/lang.yml | 10 +++ tests/pages/lang.page.json | 100 +++++++++++++++++++++ tests/pages/service/compare.page.json | 2 +- 10 files changed, 281 insertions(+), 9 deletions(-) create mode 100644 page/lang.go create mode 100644 tests/langs/zh-hk/pages/lang.yml create mode 100644 tests/pages/lang.page.json diff --git a/chart/chart_test.go b/chart/chart_test.go index afed79f0..150df114 100644 --- a/chart/chart_test.go +++ b/chart/chart_test.go @@ -40,4 +40,6 @@ func check(t *testing.T) { filters := lang.Page.Layout["filters"].([]interface{}) begin := filters[0].(map[string]interface{}) assert.Equal(t, "開始時間", begin["name"]) + + assert.Equal(t, "請選擇開始時間", lang.Filters["開始時間"].Input.Props["placeholder"]) } diff --git a/chart/lang.go b/chart/lang.go index 68c4cd30..667f7407 100644 --- a/chart/lang.go +++ b/chart/lang.go @@ -11,6 +11,19 @@ func (chart *Chart) Lang(trans func(widget string, inst string, value *string) b trans(widget, inst, &chart.Page.Primary) transMap(widget, inst, chart.Page.Layout, trans) chart.Output = transAny(widget, inst, chart.Output, trans) + + // Filters + for name, filter := range chart.Filters { + new := name + trans(widget, inst, &new) + trans(widget, inst, &filter.Label) + delete(chart.Filters, name) + + // Props + transMap(widget, inst, filter.Input.Props, trans) + chart.Filters[new] = filter + } + } func transAny(widget string, inst string, input interface{}, trans func(widget string, inst string, value *string) bool) interface{} { diff --git a/lang/lang.go b/lang/lang.go index 69534959..ddf950f1 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -12,7 +12,9 @@ import ( func init() { lang.RegisterWidget("tables", "table") lang.RegisterWidget("charts", "chart") - lang.RegisterWidget("kanban", "kanban") + lang.RegisterWidget("kanban", "page") + lang.RegisterWidget("screen", "page") + lang.RegisterWidget("pages", "page") } // Load language packs diff --git a/page/lang.go b/page/lang.go new file mode 100644 index 00000000..9342eabe --- /dev/null +++ b/page/lang.go @@ -0,0 +1,125 @@ +package page + +// Lang for applying a language pack +func (page *Page) Lang(trans func(widget string, inst string, value *string) bool) { + inst := page.Flow.Name + widget := "page" + + trans(widget, inst, &page.Name) + trans(widget, inst, &page.Label) + trans(widget, inst, &page.Description) + trans(widget, inst, &page.Page.Primary) + transMap(widget, inst, page.Page.Layout, trans) + page.Output = transAny(widget, inst, page.Output, trans) + + // Filters + for name, filter := range page.Filters { + new := name + trans(widget, inst, &new) + trans(widget, inst, &filter.Label) + delete(page.Filters, name) + + // Props + transMap(widget, inst, filter.Input.Props, trans) + page.Filters[new] = filter + } + +} + +func transAny(widget string, inst string, input interface{}, trans func(widget string, inst string, value *string) bool) interface{} { + switch input.(type) { + case []interface{}: + values := input.([]interface{}) + transArr(widget, inst, values, trans) + input = values + break + + case map[string]interface{}: + values := input.(map[string]interface{}) + for name, value := range values { + new := name + newValue := value + + switch value.(type) { + case string: + val := value.(string) + trans(widget, inst, &val) + newValue = val + break + + case []interface{}: + vals := value.([]interface{}) + transArr(widget, inst, vals, trans) + newValue = vals + break + + case map[string]interface{}: + vals := value.(map[string]interface{}) + transMap(widget, inst, vals, trans) + newValue = vals + break + } + + trans(widget, inst, &new) + delete(values, name) + values[new] = newValue + } + input = values + break + } + + return input +} + +func transMap(widget string, inst string, values map[string]interface{}, trans func(widget string, inst string, value *string) bool) { + for key, value := range values { + + switch value.(type) { + + case string: + v := value.(string) + trans(widget, inst, &v) + values[key] = v + break + + case []interface{}: + v := value.([]interface{}) + transArr(widget, inst, v, trans) + values[key] = v + break + + case map[string]interface{}: + v := value.(map[string]interface{}) + transMap(widget, inst, v, trans) + values[key] = v + break + } + + } +} + +func transArr(widget string, inst string, values []interface{}, trans func(widget string, inst string, value *string) bool) { + for key, value := range values { + switch value.(type) { + + case string: + v := value.(string) + trans(widget, inst, &v) + values[key] = v + break + + case []interface{}: + v := value.([]interface{}) + transArr(widget, inst, v, trans) + values[key] = v + break + + case map[string]interface{}: + v := value.(map[string]interface{}) + transMap(widget, inst, v, trans) + values[key] = v + break + } + + } +} diff --git a/page/page.go b/page/page.go index 8cbe9efb..98bbfe15 100644 --- a/page/page.go +++ b/page/page.go @@ -6,6 +6,7 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/lang" "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" @@ -24,15 +25,15 @@ func Load(cfg config.Config) error { var errs error = nil if err := LoadFrom(path.Join(cfg.Root, "/kanban"), ""); err != nil { - log.Trace("load kanban error: %s ", err.Error()) + // log.Trace("load kanban error: %s ", err.Error()) errs = err } if err := LoadFrom(path.Join(cfg.Root, "/screen"), ""); err != nil { - log.Trace("load screen error: %s", err.Error()) + // log.Trace("load screen error: %s", err.Error()) errs = err } if err := LoadFrom(path.Join(cfg.Root, "/pages"), ""); err != nil { - log.Trace("load screen error: %s", err.Error()) + // log.Trace("load screen error: %s", err.Error()) errs = err } @@ -93,6 +94,12 @@ func LoadPage(source []byte, name string) (*Page, error) { page.Prepare() page.SetupAPIs() Pages[name] = page + + // Apply a language pack + if lang.Default != nil { + lang.Default.Apply(Pages[name]) + } + return page, nil } diff --git a/page/page_test.go b/page/page_test.go index 652474c1..8aa90262 100644 --- a/page/page_test.go +++ b/page/page_test.go @@ -1,16 +1,22 @@ package page import ( + "os" "testing" "github.com/stretchr/testify/assert" "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/lang" "github.com/yaoapp/yao/model" "github.com/yaoapp/yao/query" "github.com/yaoapp/yao/share" ) func TestLoad(t *testing.T) { + + os.Setenv("YAO_LANG", "zh-hk") + lang.Load(config.Conf) + share.DBConnect(config.Conf.DB) model.Load(config.Conf) query.Load(config.Conf) @@ -25,5 +31,15 @@ func check(t *testing.T) { for key := range Pages { keys = append(keys, key) } - assert.Equal(t, 1, len(keys)) + assert.Equal(t, 2, len(keys)) + + lang := Pages["lang"] + output := lang.Output.(map[string]interface{}) + assert.Equal(t, "{{$in}}", output["參數"]) + + filters := lang.Page.Layout["filters"].([]interface{}) + begin := filters[0].(map[string]interface{}) + assert.Equal(t, "開始時間", begin["name"]) + + assert.Equal(t, "請選擇開始時間", lang.Filters["開始時間"].Input.Props["placeholder"]) } diff --git a/page/process_test.go b/page/process_test.go index 0065567f..ef56b914 100644 --- a/page/process_test.go +++ b/page/process_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/yaoapp/gou" "github.com/yaoapp/kun/any" - "github.com/yaoapp/kun/utils" "github.com/yaoapp/yao/config" _ "github.com/yaoapp/yao/helper" "github.com/yaoapp/yao/model" @@ -68,8 +67,6 @@ func TestProcessData(t *testing.T) { } process := gou.NewProcess("xiang.page.Data", args...) response := ProcessData(process) - utils.Dump(response) - assert.NotNil(t, response) res := any.Of(response).Map().Dot() diff --git a/tests/langs/zh-hk/pages/lang.yml b/tests/langs/zh-hk/pages/lang.yml new file mode 100644 index 00000000..0f0a4714 --- /dev/null +++ b/tests/langs/zh-hk/pages/lang.yml @@ -0,0 +1,10 @@ +Start: 開始時間 +End: 結束時間 +City: 城市 +Please select a start time: 請選擇開始時間 +Please select an end time: 請選擇結束時間 +Industry: 行業 +Billing: 計費 +Merge Reulst: 合併結果 +Parameter: 參數 +Merge: 合併 diff --git a/tests/pages/lang.page.json b/tests/pages/lang.page.json new file mode 100644 index 00000000..f2553516 --- /dev/null +++ b/tests/pages/lang.page.json @@ -0,0 +1,100 @@ +{ + "label": "::Demo", + "version": "1.0.0", + "description": "::Demo", + "nodes": [ + { + "name": "行业", + "engine": "xiang", + "query": { + "select": [ + "city", + ":COUNT(id) as 数量", + "industries[*](string 50) as industry" + ], + "from": "$service", + "wheres": [ + { "field": "created_at", ">": "?:$from.0" }, + { "field": "created_at", "<": "?:$to" } + ], + "orders": "数量 desc", + "limit": 100 + } + }, + { + "name": "计费", + "engine": "xiang", + "query": { + "select": ["city", ":COUNT(id) as 数量", "price_options[*] as option"], + "from": "$service", + "wheres": [ + { "field": "created_at", ">": "?:$from.0" }, + { "field": "created_at", "<": "?:$to" } + ], + "orders": "数量 desc", + "limit": 100 + } + }, + { + "name": "合并结果", + "process": "xiang.helper.ArrayPluck", + "args": [ + ["城市", "行业", "计费"], + { + "行业": { "key": "city", "value": "数量", "items": "{{$res.行业}}" }, + "计费": { "key": "city", "value": "数量", "items": "{{$res.计费}}" } + } + ] + } + ], + "output": { + "::Merge": "{{$res.合并结果}}", + "::Billing": "{{$res.计费}}", + "::Industry": "{{$res.行业}}", + "::Parameter": "{{$in}}" + }, + "apis": { + "data": { + "disable": false, + "guard": "-", + "default": [{ "from": "1980-01-02", "to": "2050-12-31" }] + }, + "setting": { "disable": false, "guard": "-" } + }, + "filters": { + "::Start": { + "label": "::Start", + "bind": "from", + "input": { + "type": "date", + "props": { + "placeholder": "::Please select a start time" + } + } + }, + "::End": { + "label": "::End", + "bind": "to", + "input": { + "type": "date", + "props": { + "placeholder": "::Please select an end time" + } + } + } + }, + "page": { + "primary": "::City", + "layout": { + "filters": [ + { "name": "::Start", "width": 6 }, + { "name": "::End", "width": 6 } + ], + "charts": [ + { "type": "line", "props": {} }, + { "type": "bar", "props": {} } + ] + }, + "actions": {} + } +} diff --git a/tests/pages/service/compare.page.json b/tests/pages/service/compare.page.json index 52a284a9..5478c7c6 100644 --- a/tests/pages/service/compare.page.json +++ b/tests/pages/service/compare.page.json @@ -7,7 +7,7 @@ "name": "行业", "engine": "xiang", "query": { - "debug": true, + "debug": false, "select": [ "city", ":COUNT(id) as 数量", From 7517e4652a04151ffab7c293010eb392790c172f Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 20 Aug 2022 21:25:32 +0800 Subject: [PATCH 86/96] [change] Refactoring Watch --- cmd/root.go | 1 + cmd/start.go | 20 +- go.mod | 4 +- go.sum | 4 + service/service.go | 16 ++ service/watch.go | 600 ++++++++++++++---------------------------- service/watch_test.go | 153 +++++++++++ 7 files changed, 391 insertions(+), 407 deletions(-) create mode 100644 service/watch_test.go diff --git a/cmd/root.go b/cmd/root.go index eec2ca8c..eb095106 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -45,6 +45,7 @@ var langs = map[string]string{ "NEXT:": "下一步:", "Listening": " 监听", "✨LISTENING✨": "✨服务正在运行✨", + "✨STOPPED✨": "✨服务已停止✨", "SessionPort": "会话服务端口", "Force migrate": "强制更新数据表结构", "Migrate is not allowed on production mode.": "Migrate 不能再生产环境下使用", diff --git a/cmd/start.go b/cmd/start.go index 516d6362..7c00bf7b 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -3,8 +3,10 @@ package cmd import ( "fmt" "os" + "os/signal" "path/filepath" "strings" + "syscall" "github.com/fatih/color" "github.com/spf13/cobra" @@ -24,6 +26,11 @@ var startCmd = &cobra.Command{ Short: L("Start Engine"), Long: L("Start Engine"), Run: func(cmd *cobra.Command, args []string) { + + // recive interrupt signal + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) + // defer service.Stop(func() { fmt.Println(L("Service stopped")) }) Boot() @@ -94,9 +101,18 @@ var startCmd = &cobra.Command{ if mode == "development" { service.Watch(config.Conf) } - + go service.Start() fmt.Println(color.GreenString(L("✨LISTENING✨"))) - service.Start() + + for { + select { + case <-interrupt: + service.Stop(func() { + fmt.Println(color.GreenString(L("✨STOPPED✨"))) + }) + return + } + } }, } diff --git a/go.mod b/go.mod index ac673cbd..b22f638f 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/caarlos0/env/v6 v6.9.1 github.com/elazarl/go-bindata-assetfs v1.0.1 github.com/fatih/color v1.13.0 - github.com/fsnotify/fsnotify v1.5.1 + github.com/fsnotify/fsnotify v1.5.4 github.com/gin-gonic/gin v1.7.7 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/joho/godotenv v1.3.0 @@ -63,6 +63,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/richardlehane/mscfb v1.0.3 // indirect github.com/richardlehane/msoleps v1.0.1 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect github.com/satori/go.uuid v1.2.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -76,7 +77,6 @@ require ( github.com/tidwall/rtred v0.1.2 // indirect github.com/tidwall/tinyqueue v0.1.1 // indirect github.com/ugorji/go/codec v1.1.7 // indirect - github.com/robfig/cron/v3 v3.0.1 // indirect github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 // indirect golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect golang.org/x/mod v0.4.2 // indirect diff --git a/go.sum b/go.sum index 5f1c8c17..e0131174 100644 --- a/go.sum +++ b/go.sum @@ -97,6 +97,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= @@ -581,6 +583,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -773,6 +776,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/service/service.go b/service/service.go index 5da3ca6d..cec86fbd 100644 --- a/service/service.go +++ b/service/service.go @@ -1,7 +1,10 @@ package service import ( + "context" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/share" ) @@ -68,3 +71,16 @@ func Stop(onComplete func()) { onComplete() } } + +// StopWithContext stop with timeout +func StopWithContext(ctx context.Context, onComplete func()) { + shutdown <- true + select { + case <-ctx.Done(): + log.Error("[STOP] canceled (%v)", ctx.Err()) + onComplete() + case <-shutdownComplete: + share.SessionStop() + onComplete() + } +} diff --git a/service/watch.go b/service/watch.go index 92247d3f..4f468831 100644 --- a/service/watch.go +++ b/service/watch.go @@ -1,459 +1,253 @@ package service import ( + "context" "fmt" + "io/fs" + "io/ioutil" "os" + "os/signal" "path/filepath" "strings" + "syscall" + "time" "github.com/fatih/color" + "github.com/fsnotify/fsnotify" "github.com/yaoapp/gou" - "github.com/yaoapp/yao/chart" + "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/engine" - "github.com/yaoapp/yao/page" "github.com/yaoapp/yao/share" - "github.com/yaoapp/yao/table" - "github.com/yaoapp/yao/workflow" ) -// Watch 监听应用目录文件变更 -func Watch(cfg config.Config) { - if os.Getenv("YAO_DEV") != "" { - WatchEngine(filepath.Join(os.Getenv("YAO_DEV"), "/yao")) - } - WatchModel(filepath.Join(cfg.Root, "models"), "") - WatchAPI(filepath.Join(cfg.Root, "apis"), "") - WatchFlow(filepath.Join(cfg.Root, "flows"), "") - WatchPlugin(filepath.Join(cfg.Root, "plugins")) - WatchTable(filepath.Join(cfg.Root, "tables"), "") - WatchChart(filepath.Join(cfg.Root, "charts"), "") - WatchPage(filepath.Join(cfg.Root, "pages"), "") - WatchWorkFlow(filepath.Join(cfg.Root, "workflows"), "") - - // 看板大屏 - WatchPage(filepath.Join(cfg.Root, "kanban"), "") - WatchPage(filepath.Join(cfg.Root, "screen"), "") - - // 监听脚本 & libs更新 - WatchGlobal(filepath.Join(cfg.Root, "libs")) - WatchGlobal(filepath.Join(cfg.Root, "scripts")) +var watchShutdown = make(chan bool, 1) // shutdown signal +var watchReady = make(chan bool, 1) // ready signal +var excludes = map[string]bool{"ui": true, "db": true, "data": true} +var hanlders = map[string]func(root string, file string, event string, cfg config.Config){ + "models": watchModel, } -// WatchEngine 监听监听引擎内建数据变更 -func WatchEngine(root string) { - root = share.DirAbs(root) - WatchModel(filepath.Join(root, "models"), "xiang.") - WatchAPI(filepath.Join(root, "apis"), "xiang.") - WatchFlow(filepath.Join(root, "flows"), "xiang.") - WatchTable(filepath.Join(root, "tables"), "xiang.") +// Watch the application code change for hot update +func Watch(cfg config.Config) (err error) { + go func() { err = watchStart(cfg) }() + select { + case <-watchReady: + return nil + } } -// WatchGlobal 监听通用程序更新 -func WatchGlobal(root string) { - if share.DirNotExists(root) { - return +// StopWatch stop watching the code change +func StopWatch() { + watchShutdown <- true + time.Sleep(200 * time.Millisecond) +} + +func watchStart(cfg config.Config) error { + + root := cfg.Root + + // recive interrupt signal + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) + + shutdown := make(chan bool, 1) + + watcher, err := fsnotify.NewWatcher() + if err != nil { + return err } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") { - return + defer watcher.Close() + + root, err = filepath.Abs(root) + if err != nil { + return err + } + + dirs, err := ioutil.ReadDir(root) + if err != nil { + return err + } + + // Add path + for _, dir := range dirs { + if !dir.IsDir() { + continue } - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - err := engine.Load(config.Conf) + name := dir.Name() + if _, has := hanlders[name]; !has { + continue + } + + filename := filepath.Join(root, name) + err := watcher.Add(filename) + if err != nil { + return err + } + + fmt.Println(color.GreenString("[Watch] Watching %s", name)) + log.Info("[Watch] Watching: %s", filename) + + // sub dir + err = filepath.WalkDir(filename, func(path string, d fs.DirEntry, err error) error { + if !d.IsDir() { + return nil + } + + log.Info("[Watch] Watching: %s", path) + err = watcher.Add(path) if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return + return err } - StopWithouttSession(func() { - fmt.Println(color.GreenString("Service Restarted")) - go StartWithouttSession() - }) - } - }) -} + return nil + }) -// WatchModel 监听业务接口更新 -func WatchModel(root string, prefix string) { - if share.DirNotExists(root) { - return + if err != nil { + return err + } } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - if !strings.HasSuffix(filename, ".json") { - return - } - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - _, err := gou.LoadModelReturn(string(content), name) // Reload - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) + // event hanlder + go func() { + for { + select { + case <-shutdown: + log.Info("[Watch] The event handler exit") return - } - fmt.Println(color.GreenString("Model %s Reloaded", name)) - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := gou.Models[name]; has { - delete(gou.Models, name) - fmt.Println(color.RedString("Model %s Removed", name)) - } - } - }) -} -// WatchAPI 监听业务接口更新 -func WatchAPI(root string, prefix string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { + case event, ok := <-watcher.Events: + if !ok { + return + } + relpath := strings.TrimPrefix(event.Name, root) + if strings.HasPrefix(relpath, string(os.PathSeparator)) { + relpath = strings.TrimPrefix(relpath, string(os.PathSeparator)) + } - if !strings.HasSuffix(filename, ".json") { - return - } + pi := strings.Split(relpath, string(os.PathSeparator)) + widget := pi[0] - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - _, err := gou.LoadAPIReturn(string(content), name) // Reload - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - fmt.Println(color.GreenString("API %s Reloaded", name)) + watchHanler := watchReload + if hanlder, has := hanlders[widget]; has { + watchHanler = hanlder + } - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := gou.APIs[name]; has { - delete(gou.APIs, name) - fmt.Println(color.RedString("API %s Removed", name)) - } - } - - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - StopWithouttSession(func() { - fmt.Println(color.GreenString("Service Restarted")) - go StartWithouttSession() - }) - } - }) -} - -// WatchFlow 监听业务逻辑变更 -func WatchFlow(root string, prefix string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") { - return - } - - if strings.HasSuffix(filename, ".js") { - name := prefix + share.SpecName(root, filename) - name = strings.ReplaceAll(name, ".", "/") - filename = filepath.Join(root, name+".flow.json") - } - - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - flow, err := gou.LoadFlowReturn(string(content), name) // Reload - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - - if flow != nil { // Reload Script - dir := filepath.Dir(filename) - share.Walk(dir, ".js", func(root, filename string) { - script := share.ScriptName(filename) - content := share.ReadFile(filename) - _, err := flow.LoadScriptReturn(string(content), script) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return + if _, has := excludes[widget]; !has { + base := filepath.Base(event.Name) + isdir := true + if strings.HasSuffix(base, ".yao") || strings.HasSuffix(base, ".json") { + isdir = false } - }) - } - fmt.Println(color.GreenString("Flow %s Reloaded", name)) + events := strings.Split(event.Op.String(), "|") + for _, eventType := range events { - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := gou.Flows[name]; has { - delete(gou.Flows, name) - fmt.Println(color.RedString("Flow %s Removed", name)) - } - } - }) -} + // ADD / REMOVE Watching dir + if isdir { + switch eventType { + case "CREATE": + log.Info("[Watch] Watching: %s", event.Name) + watcher.Add(event.Name) + break + case "REMOVE": + log.Info("[Watch] Unwatching: %s", event.Name) + watcher.Remove(event.Name) + break + } + continue + } -// WatchPlugin 监听业务插件变更 -func WatchPlugin(root string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - - if !strings.HasSuffix(filename, ".so") { - return - } - - if op == "write" || op == "create" { - name := share.SpecName(root, filename) - _, err := gou.LoadPluginReturn(filename, name) // Reload - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - fmt.Println(color.GreenString("Plugin %s Reloaded", name)) - - } else if op == "remove" || op == "rename" { - name := share.SpecName(root, filename) - if _, has := gou.Plugins[name]; has { - delete(gou.Plugins, name) - fmt.Println(color.RedString("Plugin %s Removed", name)) - } - } - }) -} - -// WatchTable 监听数据表格更新 -func WatchTable(root string, prefix string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - - if !strings.HasSuffix(filename, ".json") { - return - } - - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - _, err := table.LoadTable(string(content), name) // Reload Table - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - - api, has := gou.APIs["xiang.table"] - if has { - _, err := gou.LoadAPIReturn(api.Source, api.Name) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - } - fmt.Println(color.GreenString("Table %s Reloaded", name)) - - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := table.Tables[name]; has { - delete(table.Tables, name) - fmt.Println(color.RedString("Table %s Removed", name)) - } - } - - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - StopWithouttSession(func() { - fmt.Println(color.GreenString("Service Restarted")) - go StartWithouttSession() - }) - } - }) -} - -// WatchChart 监听分析图表更新 -func WatchChart(root string, prefix string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") { - return - } - - if strings.HasSuffix(filename, ".js") { - name := prefix + share.SpecName(root, filename) - name = strings.ReplaceAll(name, ".", "/") - filename = filepath.Join(root, name+".chart.json") - } - - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - chart, err := chart.LoadChart(content, name) // Relaod - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - - if chart != nil { // Reload Script - dir := filepath.Dir(filename) - share.Walk(dir, ".js", func(root, filename string) { - script := share.ScriptName(filename) - content := share.ReadFile(filename) - _, err := chart.LoadScriptReturn(string(content), script) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return + log.Info("[Watch] %s %s", eventType, event.Name) + watchHanler(root, event.Name, eventType, cfg) } - }) - } + } + break - api, has := gou.APIs["xiang.chart"] - if has { - _, err := gou.LoadAPIReturn(api.Source, api.Name) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) + case err, ok := <-watcher.Errors: + if !ok { return } - } - fmt.Println(color.GreenString("Chart %s Reloaded", name)) - - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := chart.Charts[name]; has { - delete(chart.Charts, name) - fmt.Println(color.RedString("Chart %s Removed", name)) + fmt.Println(color.RedString("[Watch] %s", err.Error())) + log.Error("[Watch] %s", err.Error()) + break } } + }() - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - StopWithouttSession(func() { - fmt.Println(color.GreenString("Service Restarted")) - go StartWithouttSession() - }) + fmt.Println(color.GreenString("[Watch] Started")) + watchReady <- true + + for { + select { + case <-watchShutdown: + shutdown <- true + log.Info("[Watch] Stopped") + fmt.Println(color.YellowString("[Watch] Stopped")) + return nil + + case <-interrupt: + shutdown <- true + log.Info("[Watch] Stopped") + fmt.Println(color.YellowString("[Watch] Stopped")) + return nil } - }) + } } -// WatchPage 监听页面更新 -func WatchPage(root string, prefix string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - if !strings.HasSuffix(filename, ".json") && !strings.HasSuffix(filename, ".js") { +func watchModel(root string, file string, event string, cfg config.Config) { + name := share.SpecName(root, file) + switch event { + case "CREATE": + content, err := ioutil.ReadFile(file) + if err != nil { + fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error())) + return + } + mod, err := gou.LoadModelReturn(string(content), name) + if err != nil { + fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error())) return } - if strings.HasSuffix(filename, ".js") { - name := prefix + share.SpecName(root, filename) - name = strings.ReplaceAll(name, ".", "/") - filename = filepath.Join(root, name+".page.json") - } + mod.Migrate(true) + fmt.Println(color.GreenString("[Watch] Model: %s Created", name)) + break - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - page, err := page.LoadPage(content, name) // Relaod - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - - if page != nil { // Reload Script - dir := filepath.Dir(filename) - share.Walk(dir, ".js", func(root, filename string) { - script := share.ScriptName(filename) - content := share.ReadFile(filename) - _, err := page.LoadScriptReturn(string(content), script) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - }) - } - - api, has := gou.APIs["xiang.page"] - if has { - _, err := gou.LoadAPIReturn(api.Source, api.Name) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - } - fmt.Println(color.GreenString("Page %s Reloaded", name)) - - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := page.Pages[name]; has { - delete(page.Pages, name) - fmt.Println(color.RedString("Page %s Removed", name)) - } - } - - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - StopWithouttSession(func() { - fmt.Println(color.GreenString("Service Restarted")) - go StartWithouttSession() - }) - } - }) -} - -// WatchWorkFlow 监听工作流更新 -func WatchWorkFlow(root string, prefix string) { - if share.DirNotExists(root) { - return - } - root = share.DirAbs(root) - go share.Watch(root, func(op string, filename string) { - if !strings.HasSuffix(filename, ".json") { + case "WRITE": + content, err := ioutil.ReadFile(file) + if err != nil { + fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error())) return } - - if op == "write" || op == "create" { - name := prefix + share.SpecName(root, filename) - content := share.ReadFile(filename) - _, err := workflow.LoadWorkFlow(content, name) // Relaod - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - - api, has := gou.APIs["xiang.workflow."+name] - if has { - _, err := gou.LoadAPIReturn(api.Source, api.Name) - if err != nil { - fmt.Println(color.RedString("Fatal: %s", err.Error())) - return - } - } - fmt.Println(color.GreenString("Workflow %s Reloaded", name)) - - } else if op == "remove" || op == "rename" { - name := prefix + share.SpecName(root, filename) - if _, has := workflow.WorkFlows[name]; has { - delete(workflow.WorkFlows, name) - fmt.Println(color.RedString("Workflow %s Removed", name)) - } + mod, err := gou.LoadModelReturn(string(content), name) + if err != nil { + fmt.Println(color.RedString("[Watch] Model: %s %s", name, err.Error())) + return } + mod.Migrate(false) + fmt.Println(color.GreenString("[Watch] Model: %s Reloaded", name)) + break - // 重启服务器 - if op == "write" || op == "create" || op == "remove" || op == "rename" { - StopWithouttSession(func() { - fmt.Println(color.GreenString("Service Restarted")) - go StartWithouttSession() - }) - } + case "REMOVE", "RENAME": + delete(gou.Models, name) + fmt.Println(color.GreenString("[Watch] Model: %s Removed", name)) + break + } +} + +func watchReload(root string, file string, event string, cfg config.Config) { + err := engine.Load(config.Conf) // 加载脚本等 + if err != nil { + fmt.Println(color.RedString("[Watch] Reload: %s", err.Error())) + } + + // Restart Server + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + StopWithContext(ctx, func() { + go Start() + fmt.Println(color.GreenString("[Watch] Reload Completed")) }) } diff --git a/service/watch_test.go b/service/watch_test.go new file mode 100644 index 00000000..e1cd9b37 --- /dev/null +++ b/service/watch_test.go @@ -0,0 +1,153 @@ +package service + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/share" +) + +func TestWatch(t *testing.T) { + + share.DBConnect(config.Conf.DB) + err := Watch(config.Conf) + if err != nil { + t.Fatal(err) + } + defer StopWatch() + + createDir(t) + renameDir(t) + + createModel(t) + changeModel(t) + renameModel(t) + removeModel(t) + + createModel(t) + removeDir(t) +} + +func TestWatchReload(t *testing.T) { + go Start() + defer Stop(func() {}) + share.DBConnect(config.Conf.DB) + watchReload("", "", "", config.Conf) +} + +func createDir(t *testing.T) { + root := config.Conf.Root + file := filepath.Join(root, "models", "watch", "test") + fmt.Println("CREATE-DIR", file) + err := os.MkdirAll(file, os.ModePerm) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} + +func renameDir(t *testing.T) { + root := config.Conf.Root + file := filepath.Join(root, "models", "watch", "test") + new := filepath.Join(root, "models", "watch", "test_new") + fmt.Println("RENAME-DIR", file) + err := os.Rename(file, new) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} + +func removeDir(t *testing.T) { + root := config.Conf.Root + file := filepath.Join(root, "models", "watch") + fmt.Println("REMOVE-DIR", file) + err := os.RemoveAll(file) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} + +func createModel(t *testing.T) { + dsl := ` + { + "name": "watch-test", + "table": { + "name": "watch_test", + "comment": "WatchTest", + "engine": "InnoDB" + }, + "columns": [ + { "name": "id", "type": "ID" }, + { "label": "Name", "name": "name", "type": "string", "index": true } + ], + "relations": {}, + "option": { "timestamps": true, "soft_deletes": true } + } + ` + root := config.Conf.Root + file := filepath.Join(root, "models", "watch", "test_new", "watch.mod.json") + fmt.Println("CREATE", file) + err := ioutil.WriteFile(file, []byte(dsl), 0644) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} + +func changeModel(t *testing.T) { + dsl := ` + { + "name": "watch-test", + "table": { + "name": "watch_test", + "comment": "WatchTest", + "engine": "InnoDB" + }, + "columns": [ + { "name": "id", "type": "ID" }, + { "label": "Name", "name": "name", "type": "string", "index": true }, + { "label": "Data", "name": "data", "type": "json", "nullable": true } + ], + "relations": {}, + "option": { "timestamps": true, "soft_deletes": true } + } + ` + root := config.Conf.Root + file := filepath.Join(root, "models", "watch", "test_new", "watch.mod.json") + fmt.Println("CHANGE", file) + err := ioutil.WriteFile(file, []byte(dsl), 0644) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} + +func renameModel(t *testing.T) { + root := config.Conf.Root + file := filepath.Join(root, "models", "watch", "test_new", "watch.mod.json") + new := filepath.Join(root, "models", "watch", "test_new", "watch_new.mod.json") + fmt.Println("RENAME", new) + err := os.Rename(file, new) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} + +func removeModel(t *testing.T) { + root := config.Conf.Root + file := filepath.Join(root, "models", "watch", "test_new", "watch_new.mod.json") + fmt.Println("REMOVE", file) + err := os.Remove(file) + if err != nil { + t.Fatal(err) + } + time.Sleep(1 * time.Second) +} From c0402149ce764a8e3761c9cb1af455b1fcd161a3 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 20 Aug 2022 21:29:00 +0800 Subject: [PATCH 87/96] [fix] spelling --- service/watch.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/service/watch.go b/service/watch.go index 4f468831..90747595 100644 --- a/service/watch.go +++ b/service/watch.go @@ -24,7 +24,7 @@ import ( var watchShutdown = make(chan bool, 1) // shutdown signal var watchReady = make(chan bool, 1) // ready signal var excludes = map[string]bool{"ui": true, "db": true, "data": true} -var hanlders = map[string]func(root string, file string, event string, cfg config.Config){ +var handlers = map[string]func(root string, file string, event string, cfg config.Config){ "models": watchModel, } @@ -76,7 +76,7 @@ func watchStart(cfg config.Config) error { } name := dir.Name() - if _, has := hanlders[name]; !has { + if _, has := handlers[name]; !has { continue } @@ -108,7 +108,7 @@ func watchStart(cfg config.Config) error { } } - // event hanlder + // event handler go func() { for { select { @@ -129,8 +129,8 @@ func watchStart(cfg config.Config) error { widget := pi[0] watchHanler := watchReload - if hanlder, has := hanlders[widget]; has { - watchHanler = hanlder + if handler, has := handlers[widget]; has { + watchHanler = handler } if _, has := excludes[widget]; !has { From 9d19d32fef94861794a54814b7204c029423c2b0 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 20 Aug 2022 22:10:49 +0800 Subject: [PATCH 88/96] [fix] watch the script file changes --- service/watch.go | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/service/watch.go b/service/watch.go index 90747595..6d3ecafe 100644 --- a/service/watch.go +++ b/service/watch.go @@ -76,13 +76,18 @@ func watchStart(cfg config.Config) error { } name := dir.Name() - if _, has := handlers[name]; !has { + if _, has := excludes[name]; has { + continue + } + + if strings.HasPrefix(name, ".") { continue } filename := filepath.Join(root, name) err := watcher.Add(filename) if err != nil { + log.Error("[Watch] %s", err.Error()) return err } @@ -90,7 +95,13 @@ func watchStart(cfg config.Config) error { log.Info("[Watch] Watching: %s", filename) // sub dir + depth := 0 err = filepath.WalkDir(filename, func(path string, d fs.DirEntry, err error) error { + depth = depth + 1 + if depth == 1 { + return nil + } + if !d.IsDir() { return nil } @@ -98,13 +109,13 @@ func watchStart(cfg config.Config) error { log.Info("[Watch] Watching: %s", path) err = watcher.Add(path) if err != nil { - return err + log.Error("[Watch] %s", err.Error()) } return nil }) if err != nil { - return err + log.Error("[Watch] %s", err.Error()) } } @@ -136,7 +147,7 @@ func watchStart(cfg config.Config) error { if _, has := excludes[widget]; !has { base := filepath.Base(event.Name) isdir := true - if strings.HasSuffix(base, ".yao") || strings.HasSuffix(base, ".json") { + if strings.HasSuffix(base, ".yao") || strings.HasSuffix(base, ".json") || strings.HasSuffix(base, ".js") { isdir = false } @@ -237,17 +248,21 @@ func watchModel(root string, file string, event string, cfg config.Config) { } func watchReload(root string, file string, event string, cfg config.Config) { - err := engine.Load(config.Conf) // 加载脚本等 - if err != nil { - fmt.Println(color.RedString("[Watch] Reload: %s", err.Error())) + + switch event { + case "CREATE", "WRITE", "REMOVE": + err := engine.Load(config.Conf) // 加载脚本等 + if err != nil { + fmt.Println(color.RedString("[Watch] Reload: %s", err.Error())) + } + + // Restart Server + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + StopWithContext(ctx, func() { + go Start() + fmt.Println(color.GreenString("[Watch] Reload Completed")) + }) } - - // Restart Server - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - StopWithContext(ctx, func() { - go Start() - fmt.Println(color.GreenString("[Watch] Reload Completed")) - }) } From 3d21a9e19ee3a56729ed237202a4b09f166a2247 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 22 Aug 2022 15:10:07 +0800 Subject: [PATCH 89/96] [add] migrate command reset & update schema --- cmd/migrate.go | 45 ++++++++++++++++++++++++++++++++++++++++----- cmd/restore.go | 2 +- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/cmd/migrate.go b/cmd/migrate.go index a6ac9f73..1115a646 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -15,6 +15,7 @@ import ( var name string var force bool = false +var resetModel bool = false var migrateCmd = &cobra.Command{ Use: "migrate", Short: L("Update database schema"), @@ -43,23 +44,57 @@ var migrateCmd = &cobra.Command{ if name != "" { mod, has := gou.Models[name] - if has { - mod.Migrate(true) + fmt.Printf(color.WhiteString(L("Update schema model: %s (%s) "), mod.Name, mod.MetaData.Table.Name) + "\t") + + if !has { + fmt.Println(color.RedString(L("Model: %s does not exits"), name)) + return } + + if resetModel { + err := mod.DropTable() + if err != nil { + fmt.Printf(color.RedString(L("FAILURE\n%s"), err.Error()) + "\n") + return + } + } + + err := mod.Migrate(false) + if err != nil { + fmt.Printf(color.RedString(L("FAILURE\n%s"), err.Error()) + "\n") + return + } + + fmt.Printf(color.GreenString(L("SUCCESS")) + "\n") return } // Do Stuff Here for _, mod := range gou.Models { - fmt.Println(color.GreenString(L("Update schema model: %s (%s) "), mod.Name, mod.MetaData.Table.Name)) - mod.Migrate(true) + fmt.Printf(color.WhiteString(L("Update schema model: %s (%s) "), mod.Name, mod.MetaData.Table.Name) + "\t") + + if resetModel { + err := mod.DropTable() + if err != nil { + fmt.Printf(color.RedString(L("FAILURE\n%s"), err.Error()) + "\n") + continue + } + } + + err := mod.Migrate(false) + if err != nil { + fmt.Printf(color.RedString(L("FAILURE\n%s"), err.Error()) + "\n") + continue + } + fmt.Printf(color.GreenString(L("SUCCESS")) + "\n") } - fmt.Println(color.GreenString(L("✨DONE✨"))) + // fmt.Println(color.GreenString(L("✨DONE✨"))) }, } func init() { migrateCmd.PersistentFlags().StringVarP(&name, "name", "n", "", L("Model name")) migrateCmd.PersistentFlags().BoolVarP(&force, "force", "", false, L("Force migrate")) + migrateCmd.PersistentFlags().BoolVarP(&resetModel, "reset", "", false, L("Drop the table if exist")) } diff --git a/cmd/restore.go b/cmd/restore.go index 248e4fc9..107f7e7b 100644 --- a/cmd/restore.go +++ b/cmd/restore.go @@ -116,7 +116,7 @@ func restoreModels(basePath string) { for _, mod := range gou.Models { fmt.Printf("\r%s", strings.Repeat(" ", 80)) fmt.Printf(color.GreenString(L("\rUpdate schema model: %s (%s) "), mod.Name, mod.MetaData.Table.Name)) - err := mod.ForceCreateSchema() + err := mod.Migrate(true) if err != nil { fmt.Println(color.RedString(L("Fatal: %s"), err.Error())) os.Exit(1) From 8bcab3230fbaa078c5ed5b6bf04e2b6b4fe82f1c Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 22 Aug 2022 15:50:15 +0800 Subject: [PATCH 90/96] [add] show tasks and schedules when starting --- cmd/start.go | 138 +++++++++++++++++++++++++------- tests/widgets/dyform/compile.js | 2 +- 2 files changed, 108 insertions(+), 32 deletions(-) diff --git a/cmd/start.go b/cmd/start.go index 7c00bf7b..2ab70fd2 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -11,7 +11,9 @@ import ( "github.com/fatih/color" "github.com/spf13/cobra" "github.com/yaoapp/gou" + "github.com/yaoapp/gou/task" "github.com/yaoapp/gou/websocket" + "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/engine" "github.com/yaoapp/yao/service" @@ -54,34 +56,6 @@ var startCmd = &cobra.Command{ host = "127.0.0.1" } - if mode == "development" || mode == "production" { - fmt.Println(color.WhiteString("\n---------------------------------")) - fmt.Println(color.WhiteString(L("API List"))) - fmt.Println(color.WhiteString("---------------------------------")) - - for _, api := range gou.APIs { // API信息 - if len(api.HTTP.Paths) <= 0 { - continue - } - - fmt.Printf(color.CyanString("\n%s(%d)\n", api.Name, len(api.HTTP.Paths))) - for _, p := range api.HTTP.Paths { - fmt.Println( - colorMehtod(p.Method), - color.WhiteString(filepath.Join("/api", api.HTTP.Group, p.Path)), - "\tprocess:", p.Process) - } - } - - fmt.Printf(color.CyanString("\n%s(%d)\n", "WebSocket", len(websocket.Upgraders))) - for name, upgrader := range websocket.Upgraders { // WebSocket - fmt.Println( - colorMehtod("GET"), - color.WhiteString(filepath.Join("/websocket", name)), - "\tprocess:", upgrader.Process) - } - } - fmt.Println(color.WhiteString("\n---------------------------------")) fmt.Println(color.WhiteString(share.App.Name), color.WhiteString(share.App.Version), mode) fmt.Println(color.WhiteString("---------------------------------")) @@ -95,12 +69,26 @@ var startCmd = &cobra.Command{ fmt.Println(color.WhiteString(L("API")), color.GreenString(" http://%s%s/api", host, port)) fmt.Println(color.WhiteString(L("Listening")), color.GreenString(" %s:%d", config.Conf.Host, config.Conf.Port)) - fmt.Println("") - - // 调试模式 + // development mode if mode == "development" { + printApis(false) + printTasks(false) + printSchedules(false) + + // Watching + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(L("Watching"))) + fmt.Println(color.WhiteString("---------------------------------")) service.Watch(config.Conf) } + + if mode == "production" { + printApis(true) + printTasks(true) + printSchedules(true) + } + + // Start server go service.Start() fmt.Println(color.GreenString(L("✨LISTENING✨"))) @@ -116,6 +104,94 @@ var startCmd = &cobra.Command{ }, } +func printSchedules(silent bool) { + if silent { + for name, sch := range gou.Schedules { + process := fmt.Sprintf("Process: %s", sch.Process) + if sch.TaskName != "" { + process = fmt.Sprintf("Task: %s", sch.TaskName) + } + log.Info("[Schedule] %s %s %s %s", sch.Schedule, name, sch.Name, process) + } + return + } + + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(L("Schedules List (%d)"), len(gou.Schedules))) + fmt.Println(color.WhiteString("---------------------------------")) + for name, sch := range gou.Schedules { + process := fmt.Sprintf("Process: %s", sch.Process) + if sch.TaskName != "" { + process = fmt.Sprintf("Task: %s", sch.TaskName) + } + fmt.Printf(color.CyanString("[Schedule] %s %s", sch.Schedule, name)) + fmt.Printf(color.WhiteString("\t%s\t%s\n", sch.Name, process)) + } +} + +func printTasks(silent bool) { + + if silent { + for _, t := range task.Tasks { + log.Info("[Task] %s workers:%d", t.Option.Name, t.Option.WorkerNums) + } + return + } + + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(L("Tasks List (%d)"), len(task.Tasks))) + fmt.Println(color.WhiteString("---------------------------------")) + for _, t := range task.Tasks { + fmt.Printf(color.CyanString("[Task] %s", t.Option.Name)) + fmt.Printf(color.WhiteString("\t workers: %d\n", t.Option.WorkerNums)) + } +} + +func printApis(silent bool) { + + if silent { + for _, api := range gou.APIs { + if len(api.HTTP.Paths) <= 0 { + continue + } + log.Info("[API] %s(%d)", api.Name, len(api.HTTP.Paths)) + for _, p := range api.HTTP.Paths { + log.Info("%s %s %s", p.Method, filepath.Join("/api", api.HTTP.Group, p.Path), p.Process) + } + } + for name, upgrader := range websocket.Upgraders { // WebSocket + log.Info("[WebSocket] GET /websocket/%s process:%s", name, upgrader.Process) + } + return + } + + fmt.Println(color.WhiteString("\n---------------------------------")) + fmt.Println(color.WhiteString(L("API List"))) + fmt.Println(color.WhiteString("---------------------------------")) + + for _, api := range gou.APIs { // API信息 + if len(api.HTTP.Paths) <= 0 { + continue + } + + fmt.Printf(color.CyanString("\n%s(%d)\n", api.Name, len(api.HTTP.Paths))) + for _, p := range api.HTTP.Paths { + fmt.Println( + colorMehtod(p.Method), + color.WhiteString(filepath.Join("/api", api.HTTP.Group, p.Path)), + "\tprocess:", p.Process) + } + } + + fmt.Printf(color.CyanString("\n%s(%d)\n", "WebSocket", len(websocket.Upgraders))) + for name, upgrader := range websocket.Upgraders { // WebSocket + fmt.Println( + colorMehtod("GET"), + color.WhiteString(filepath.Join("/websocket", name)), + "\tprocess:", upgrader.Process) + } +} + func colorMehtod(method string) string { method = strings.ToUpper(method) switch method { diff --git a/tests/widgets/dyform/compile.js b/tests/widgets/dyform/compile.js index 2a4ec0b5..308b0c31 100644 --- a/tests/widgets/dyform/compile.js +++ b/tests/widgets/dyform/compile.js @@ -28,7 +28,7 @@ function Compile(name, dsl) { * @param {DSL} dsl */ function OnLoad(name, dsl) { - console.log(name, dsl); + log.Info("[Widget] dyform.OnLoad %s %v", name, dsl); } /** From 11da4cea39c541fc56eeca18943951b6e6ed5359 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 22 Aug 2022 16:13:56 +0800 Subject: [PATCH 91/96] [add] v0.10.1 release prepare --- share/const.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/const.go b/share/const.go index 5d7e5b8f..6788904c 100644 --- a/share/const.go +++ b/share/const.go @@ -1,7 +1,7 @@ package share // VERSION 版本号 -const VERSION = "0.10.0" +const VERSION = "0.10.1" // PRVERSION PreRelease Version const PRVERSION = "DEV" From 81ba5668f28ce325cf01b4a925c246281082fd26 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 22 Aug 2022 18:51:44 +0800 Subject: [PATCH 92/96] [change] print message when loading error --- api/api.go | 9 +++++++++ engine/load.go | 48 ++++++++++++++++++++++++++++++------------------ table/table.go | 6 ++++++ 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/api/api.go b/api/api.go index 91e7e3c3..fb76b859 100644 --- a/api/api.go +++ b/api/api.go @@ -3,6 +3,7 @@ package api import ( "fmt" "path/filepath" + "strings" "github.com/yaoapp/gou" "github.com/yaoapp/kun/log" @@ -23,12 +24,15 @@ func LoadFrom(dir string, prefix string) error { if share.DirNotExists(dir) { return fmt.Errorf("%s does not exists", dir) } + + messages := []string{} err := share.Walk(dir, ".http.json", func(root, filename string) { name := prefix + share.SpecName(root, filename) content := share.ReadFile(filename) _, err := gou.LoadAPIReturn(string(content), name, "bearer-jwt") if err != nil { log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + messages = append(messages, fmt.Sprintf("%s %s", name, err.Error())) } }) @@ -39,9 +43,14 @@ func LoadFrom(dir string, prefix string) error { _, err := gou.LoadWebSocketServer(string(content), name) if err != nil { log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + messages = append(messages, fmt.Sprintf("%s %s", name, err.Error())) } }) + if len(messages) > 0 { + return fmt.Errorf("%s", strings.Join(messages, ";")) + } + return err } diff --git a/engine/load.go b/engine/load.go index c35bae19..87c5aa29 100644 --- a/engine/load.go +++ b/engine/load.go @@ -4,10 +4,11 @@ import ( "fmt" "os" "path/filepath" + "strings" + "github.com/fatih/color" "github.com/yaoapp/gou" "github.com/yaoapp/kun/exception" - "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/api" "github.com/yaoapp/yao/app" "github.com/yaoapp/yao/cert" @@ -45,7 +46,7 @@ func Load(cfg config.Config) (err error) { // Load Certs err = cert.Load(cfg) if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "Cert", err) } // 第二步: 建立数据库 & 会话连接 @@ -65,83 +66,94 @@ func Load(cfg config.Config) (err error) { // 第四步: 加载共享库 & JS 处理器 err = share.Load(cfg) // 加载共享库 lib if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "Lib", err) } err = script.Load(cfg) // 加载JS处理器 script if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "Script", err) } // 第五步: 加载数据模型等 err = model.Load(cfg) // 加载数据模型 model if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "Model", err) } err = flow.Load(cfg) // 加载业务逻辑 Flow if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "Flow", err) } err = store.Load(cfg) // Load stores if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "Store", err) } err = plugin.Load(cfg) // 加载业务插件 plugin if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "Plugin", err) } err = table.Load(cfg) // 加载数据表格 table if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "Table", err) } err = chart.Load(cfg) // 加载分析图表 chart if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "Chart", err) + } + + err = page.Load(cfg) // 加载页面 page 忽略错误 + if err != nil { + printErr(cfg.Mode, "Page", err) } - page.Load(cfg) // 加载页面 page 忽略错误 importer.Load(cfg) // 加载数据导入 imports // workflow.Load(cfg) // 加载工作流 workflow err = api.Load(cfg) // 加载业务接口 API if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "API", err) } err = socket.Load(cfg) // Load sockets if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "Socket", err) } err = websocket.Load(cfg) // Load websockets (client) if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "WebSocket", err) } err = task.Load(cfg) // Load tasks if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "Task", err) } err = schedule.Load(cfg) // Load schedules if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "Schedule", err) } - err = widget.Load(cfg) // Load schedules + err = widget.Load(cfg) // Load widgets if err != nil { - log.Debug(err.Error()) + printErr(cfg.Mode, "Widget", err) } return nil } +func printErr(mode, widget string, err error) { + message := fmt.Sprintf("[%s] %s", widget, err.Error()) + if !strings.Contains(message, "does not exists") && mode == "development" { + color.Red(message) + } +} + // Reload 根据配置重新加载 API, FLow, Model, Plugin func Reload(cfg config.Config) { gou.APIs = map[string]*gou.API{} diff --git a/table/table.go b/table/table.go index 765bbdd7..39b24125 100644 --- a/table/table.go +++ b/table/table.go @@ -127,15 +127,21 @@ func LoadFrom(dir string, prefix string) error { return fmt.Errorf("%s does not exists", dir) } + messages := []string{} err := share.Walk(dir, ".json", func(root, filename string) { name := share.SpecName(root, filename) content := share.ReadFile(filename) _, err := LoadTable(string(content), name) if err != nil { log.With(log.F{"root": root, "file": filename}).Error(err.Error()) + messages = append(messages, fmt.Sprintf("%s %s", name, err.Error())) } }) + if len(messages) > 0 { + return fmt.Errorf("%s", strings.Join(messages, ";")) + } + return err } From b538fd87471d5a95b437f274481c27f5d4aab223 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 28 Aug 2022 23:08:28 +0800 Subject: [PATCH 93/96] [add] start --disable-watching --- cmd/start.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cmd/start.go b/cmd/start.go index 2ab70fd2..a712cd72 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -1,12 +1,14 @@ package cmd import ( + "context" "fmt" "os" "os/signal" "path/filepath" "strings" "syscall" + "time" "github.com/fatih/color" "github.com/spf13/cobra" @@ -21,7 +23,7 @@ import ( ) var startDebug = false -var startAlpha = false +var startDisableWatching = false var startCmd = &cobra.Command{ Use: "start", @@ -74,7 +76,9 @@ var startCmd = &cobra.Command{ printApis(false) printTasks(false) printSchedules(false) + } + if mode == "development" && !startDisableWatching { // Watching fmt.Println(color.WhiteString("\n---------------------------------")) fmt.Println(color.WhiteString(L("Watching"))) @@ -95,7 +99,9 @@ var startCmd = &cobra.Command{ for { select { case <-interrupt: - service.Stop(func() { + ctx, canceled := context.WithTimeout(context.Background(), (5 * time.Second)) + defer canceled() + service.StopWithContext(ctx, func() { fmt.Println(color.GreenString(L("✨STOPPED✨"))) }) return @@ -206,5 +212,5 @@ func colorMehtod(method string) string { func init() { startCmd.PersistentFlags().BoolVarP(&startDebug, "debug", "", false, L("Development mode")) - startCmd.PersistentFlags().BoolVarP(&startAlpha, "alpha", "", false, L("Enabled unstable features")) + startCmd.PersistentFlags().BoolVarP(&startDisableWatching, "disable-watching", "", false, L("Disable watching")) } From f5ffbd8f256b9aadccdd6f8ba62b09053cdb2c6a Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 29 Aug 2022 19:54:08 +0800 Subject: [PATCH 94/96] [chnage] README --- README.md | 22 +++++++++++++++------- README.zh-CN.md | 18 ++++++++++++++---- docs/architecture.png | Bin 0 -> 295428 bytes 3 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 docs/architecture.png diff --git a/README.md b/README.md index 4c068fab..62f66c9e 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ Twitter · - - Slack + + Discord

@@ -35,17 +35,25 @@

-![intro](docs/1.intro.png) +![intro](docs/architecture.png) [中文介绍](README.zh-CN.md) +**Discord:** https://discord.gg/nsKmCXwvxU + +**Documentation:** https://yaoapps.com/en-US/doc + ## Demo ### Customer Relationship Management System A Customer Relationship Management System developed by Yao. -[https://demo-crm.yaoapps.com](https://demo-crm.yaoapps.com/xiang/login/admin?autoLogin=true) +DEMO: [View Demo](https://demo-crm.yaoapps.com/xiang/login/admin?autoLogin=true) + +SOURCE: [https://github.com/YaoApp/demo-crm](https://github.com/YaoApp/demo-crm) + +INFRA: https://letsinfra.com/openapp/crm ### Intelligent warehouse management system @@ -55,7 +63,7 @@ An example of cloud + edge IoT application, an unattended intelligent warehouse ## Introduce -**Yao allows developers to create web services by processes.** Yao is a low-code engine that creates a database model, writes API services, and describes dashboard interface just by JSON for web & hardware, no code, and 10x productivity. +**Yao allows developers to create web services by processes.** Yao is a app engine that creates a database model, writes API services, and describes dashboard interface just by JSON for web & hardware, and 10x productivity. Yao is based on the **flow-based** programming idea, developed in the **Go** language, and supports multiple ways to expand the data stream processor. This makes Yao extremely versatile, which can replace programming languages ​​in most scenarios, and is 10 times more efficient than traditional programming languages ​​in terms of reusability and coding efficiency; application performance and resource ratio Better than **PHP**, **JAVA** and other languages. @@ -69,7 +77,7 @@ Run the script under terminal: (MacOS/Linux) curl -fsSL https://website.yaoapps.com/install.sh | bash ``` -For Windows users, please refer to the Installation and Debugging chapter: [Installation and debugging](https://yaoapps.com/en-US/doc/a.Introduction/b.Install) +For Windows users, please refer to the Installation and Debugging chapter: [Installation and debugging](https://yaoapps.com/en-US/doc/Introduction/Install) ## Getting Started @@ -121,4 +129,4 @@ yao start ## About Yao -Yao's name is derived from the Chinese character **yao (yáo)**, the basic symbol that makes up the Eight Trigrams. The Eight Trigrams is a symbol system created by the ancient god Fuxi after observing and summarizing the laws of nature, which can refer to everything. Yao has two states of yin and yang, like 0 and 1. The transformation of yin and yang of Yao drives the replacement of Eight Trigrams, so as to summarize and record the development law of things. +Yao's name is derived from the Chinese character **爻 (yáo)**, the basic symbol that makes up the Eight Trigrams. The Eight Trigrams is a symbol system created by the ancient god Fuxi after observing and summarizing the laws of nature, which can refer to everything. Yao has two states of yin and yang, like 0 and 1. The transformation of yin and yang of Yao drives the replacement of Eight Trigrams, so as to summarize and record the development law of things. diff --git a/README.zh-CN.md b/README.zh-CN.md index 207319c2..41d719b2 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -5,7 +5,7 @@ ![intro](docs/1.intro.png) -Yao 是一款支持快速创建 Web 服务和管理后台的开源低代码应用引擎。 +YAO 是一款开源应用引擎,使用 Golang 编写,以一个命令行工具的形式存在, 下载即用。适合用于开发业务系统、网站/APP API 接口、管理后台、自建低代码平台等。 [English](README.md) @@ -13,23 +13,33 @@ Yao 是一款支持快速创建 Web 服务和管理后台的开源低代码应 文档: [https://yaoapps.com/doc](https://yaoapps.com/doc) +微信群: 添加微信 17192175099 备注(开发交流) + ## 演示 ### 客户管理系统 使用 Yao 搭建的一套通用 CRM 管理系统。 -[https://demo-crm.yaoapps.com](https://demo-crm.yaoapps.com/xiang/login/admin?autoLogin=true) +演示: [查看演示](https://demo-crm.yaoapps.com/xiang/login/admin?autoLogin=true) + +源码: https://github.com/YaoApp/demo-crm + +Infra 一键部署: https://letsinfra.com/openapp/crm ### 智能仓库管理系统 使用 Yao 搭建的云+边物联网应用,支持人脸识别、RFID 的无人值守智能仓库管理系统。 -[https://demo-wms.yaoapps.com](https://demo-crm.yaoapps.com/xiang/login/admin?autoLogin=true) +演示: [查看演示](https://demo-crm.yaoapps.com/xiang/login/admin?autoLogin=true) + +源码: https://github.com/YaoApp/yao-wms + +Infra 一键部署: https://letsinfra.com/openapp/wms ## 介绍 -Yao 是一个只需使用 JSON 即可创建数据库模型、编写 API 接口、描述管理后台界面的低代码引擎,使用 Yao 构建的应用可运行在云端或物联网设备上。 开发者不需要写一行代码,就可以拥有 10 倍生产力。 +Yao 是一个只需使用 JSON 即可创建数据库模型、编写 API 接口、描述管理后台界面的应用引擎,使用 Yao 构建的应用可运行在云端或物联网设备上。 开发者不需要写一行代码,就可以拥有 10 倍生产力。 Yao 基于 **flow-based** 编程思想,采用 **Go** 语言开发,支持多种方式扩展数据流处理器。这使得 Yao 具有极好的**通用性**,大部分场景下可以代替编程语言, 在复用性和编码效率上是传统编程语言的 **10 倍**;应用性能和资源占比上优于 **PHP**, **JAVA** 等语言。 diff --git a/docs/architecture.png b/docs/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..841fd5ca653c74d1707fe7896b319189ef1a8d11 GIT binary patch literal 295428 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

BV^SvDW|4-anpZW4dd}fy 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�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 Ut+%4`_kbU{o66ETHw+&AfB#!#*Z=?k literal 0 HcmV?d00001 From 7dfd0333b1bb7d023ada6600dcc935f1c43b2c25 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 29 Aug 2022 20:00:33 +0800 Subject: [PATCH 95/96] [change] Introduction --- README.md | 10 ++++++++++ README.zh-CN.md | 12 ++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 62f66c9e..848a534e 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,16 @@ [中文介绍](README.zh-CN.md) +YAO is an open-source application engine, written in Golang, in the form of a command-line tool that can be downloaded and used immediately. It is suitable for developing business systems, website/APP API, admin panel, self-built low-code platforms, etc. + +YAO adopts a flow-based programming model to implement various functions by writing YAO DSL (Logical Description in JSON format) or using JavaScript to write processes. The YAO DSL can be written in several ways: + +1. Purely hand-written + +2. Use automated scripts to generate contextual logic + +3. Use the visual editor to create by "drag and drop" + **Discord:** https://discord.gg/nsKmCXwvxU **Documentation:** https://yaoapps.com/en-US/doc diff --git a/README.zh-CN.md b/README.zh-CN.md index 41d719b2..ef5828a6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -3,11 +3,19 @@ [![UnitTest](https://github.com/YaoApp/yao/actions/workflows/unit-test.yml/badge.svg)](https://github.com/YaoApp/yao/actions/workflows/unit-test.yml) [![codecov](https://codecov.io/gh/YaoApp/yao/branch/main/graph/badge.svg?token=294Y05U71J)](https://codecov.io/gh/YaoApp/yao) -![intro](docs/1.intro.png) +![intro](docs/architecture.png) + +[English](README.md) YAO 是一款开源应用引擎,使用 Golang 编写,以一个命令行工具的形式存在, 下载即用。适合用于开发业务系统、网站/APP API 接口、管理后台、自建低代码平台等。 -[English](README.md) +YAO 采用 flow-based 的编程模式,通过编写 YAO DSL (JSON 格式逻辑描述) 或使用 JavaScript 编写处理器,实现各种功能。 YAO DSL 可以有多种编写方式: + +1. 纯手工编写 + +2. 使用自动化脚本,根据上下文逻辑生成 + +3. 使用可视化编辑器,通过“拖拉拽”制作 官网: [https://yaoapps.com](https://yaoapps.com) From babf48ef381a1d016608001ae9f7a5602f8e76d2 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 29 Aug 2022 20:37:35 +0800 Subject: [PATCH 96/96] [add] wechat qrcode --- README.zh-CN.md | 2 ++ docs/wechat.jpg | Bin 0 -> 63945 bytes 2 files changed, 2 insertions(+) create mode 100644 docs/wechat.jpg diff --git a/README.zh-CN.md b/README.zh-CN.md index ef5828a6..459402e8 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -23,6 +23,8 @@ YAO 采用 flow-based 的编程模式,通过编写 YAO DSL (JSON 格式逻辑 微信群: 添加微信 17192175099 备注(开发交流) +![微信群](docs/wechat.jpg) + ## 演示 ### 客户管理系统 diff --git a/docs/wechat.jpg b/docs/wechat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..84ad1bd84161cbbe564cf415676b7cca3d55a347 GIT binary patch literal 63945 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

yb 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 NtpES=2iWhi{|B|ae*gdg literal 0 HcmV?d00001