├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── codeql.yml
│ ├── deploy.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── README_ZH.md
├── cmd
├── eagle
│ ├── README.md
│ ├── go.mod
│ ├── go.sum
│ ├── internal
│ │ ├── base
│ │ │ ├── get.go
│ │ │ ├── install.go
│ │ │ ├── mod.go
│ │ │ ├── mod_test.go
│ │ │ ├── path.go
│ │ │ ├── repo.go
│ │ │ ├── repo_test.go
│ │ │ ├── vcs_url.go
│ │ │ └── vcs_url_test.go
│ │ ├── cache
│ │ │ ├── add
│ │ │ │ ├── add.go
│ │ │ │ ├── cache.go
│ │ │ │ └── template.go
│ │ │ └── cache.go
│ │ ├── handler
│ │ │ ├── add
│ │ │ │ ├── add.go
│ │ │ │ ├── handler.go
│ │ │ │ └── template.go
│ │ │ └── handler.go
│ │ ├── model
│ │ │ ├── model.go
│ │ │ └── new.go
│ │ ├── project
│ │ │ ├── new.go
│ │ │ └── project.go
│ │ ├── proto
│ │ │ ├── add
│ │ │ │ ├── add.go
│ │ │ │ ├── proto.go
│ │ │ │ └── template.go
│ │ │ ├── client
│ │ │ │ └── client.go
│ │ │ ├── proto.go
│ │ │ └── server
│ │ │ │ ├── server.go
│ │ │ │ └── template.go
│ │ ├── repo
│ │ │ ├── add
│ │ │ │ ├── add.go
│ │ │ │ ├── repo.go
│ │ │ │ └── template.go
│ │ │ └── repo.go
│ │ ├── run
│ │ │ └── run.go
│ │ ├── service
│ │ │ ├── add
│ │ │ │ ├── add.go
│ │ │ │ ├── service.go
│ │ │ │ └── template.go
│ │ │ └── service.go
│ │ ├── task
│ │ │ ├── add
│ │ │ │ ├── add.go
│ │ │ │ ├── task.go
│ │ │ │ └── template.go
│ │ │ ├── list
│ │ │ │ └── list.go
│ │ │ └── task.go
│ │ ├── upgrade
│ │ │ └── upgrade.go
│ │ └── utils
│ │ │ └── utils.go
│ └── main.go
└── protoc-gen-go-gin
│ ├── gin.go
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ └── template.go
├── config
├── docker
│ ├── README.md
│ ├── app.yaml
│ ├── config.yaml
│ ├── database.yaml
│ ├── logger.yaml
│ ├── nginx_api.conf
│ ├── redis.yaml
│ └── trace.yaml
└── local
│ ├── README.md
│ ├── app.yaml
│ ├── config.yaml
│ ├── database.yaml
│ ├── kafka.yaml
│ ├── logger.yaml
│ ├── nginx_api.conf
│ ├── rabbitmq.yaml
│ ├── redis.yaml
│ └── trace.yaml
├── deploy
├── README.md
├── docker
│ └── mysql
│ │ ├── init.sql
│ │ └── my.cnf
├── grafana
│ ├── alertmanager.yml
│ └── rules.yml
├── k8s
│ ├── aws-auth.yaml
│ ├── consul-server-http.yaml
│ ├── consul-server-service.yaml
│ ├── consul-server.yaml
│ ├── etcd-StatefulSet.yaml
│ ├── etcd-service.yaml
│ ├── etcd-ui-configmap.yaml
│ ├── etcd-ui-deployment-service.yaml
│ ├── etcd-ui.yaml
│ ├── etcd.yml.tmpl
│ ├── etcd_config.bash
│ ├── go-deployment.yaml
│ ├── go-ingress.yaml
│ └── go-service.yaml
└── prometheus
│ ├── README.md
│ └── prometheus.yml
├── docker-compose.yml
├── docs
└── images
│ ├── clean-arch.png
│ ├── eagle-layout-arch.excalidraw
│ └── eagle-layout-arch.png
├── examples
├── config
│ ├── config.yaml
│ ├── main.go
│ └── server.yaml
├── crontab
│ ├── app.yaml
│ ├── crontab.yaml
│ ├── jobs.go
│ ├── logger.yaml
│ └── server.go
├── helloworld
│ ├── README.md
│ ├── client
│ │ └── main.go
│ ├── helloworld
│ │ ├── greeter.pb.go
│ │ ├── greeter.proto
│ │ └── greeter_grpc.pb.go
│ └── server
│ │ └── main.go
├── log
│ ├── logger.yaml
│ └── main.go
├── queue
│ ├── kafka
│ │ ├── config
│ │ │ ├── app.yaml
│ │ │ ├── kafka.yaml
│ │ │ └── logger.yaml
│ │ └── main.go
│ ├── nats
│ │ └── main.go
│ ├── rabbitmq
│ │ ├── consumer
│ │ │ ├── config
│ │ │ │ ├── app.yaml
│ │ │ │ ├── logger.yaml
│ │ │ │ └── rabbitmq.yaml
│ │ │ ├── main.go
│ │ │ └── server.go
│ │ └── producer
│ │ │ ├── config
│ │ │ ├── app.yaml
│ │ │ ├── logger.yaml
│ │ │ └── rabbitmq.yaml
│ │ │ ├── delay_publish.go
│ │ │ └── main.go
│ └── redis
│ │ ├── app.yaml
│ │ ├── client.go
│ │ ├── handler.go
│ │ ├── logger.yaml
│ │ ├── producer.go
│ │ ├── redis.yaml
│ │ └── server.go
├── registry
│ └── main.go
└── trace
│ └── main.go
├── go.mod
├── go.sum
├── internal
├── cache
│ ├── cache_client.go
│ └── user_base.go
├── ecode
│ ├── README.md
│ └── user.go
├── handler
│ └── v1
│ │ └── user
│ │ ├── follow.go
│ │ ├── follow_list.go
│ │ ├── follower_list.go
│ │ ├── get.go
│ │ ├── login.go
│ │ ├── register.go
│ │ ├── unfollow.go
│ │ ├── update.go
│ │ ├── user.go
│ │ └── vcode.go
├── middleware
│ ├── README.md
│ └── translations.go
├── model
│ ├── README.md
│ ├── init.go
│ ├── model.go
│ ├── user_base.go
│ ├── user_fans.go
│ ├── user_follow.go
│ └── user_stat.go
├── pkg
│ ├── README.md
│ ├── counter.go
│ ├── idalloc.go
│ └── repeat.go
├── repository
│ ├── README.md
│ ├── repository.go
│ ├── user_base_repo.go
│ ├── user_follow_repo.go
│ └── user_stat_repo.go
├── routers
│ ├── router.go
│ └── web.go
├── server
│ ├── grpc.go
│ └── http.go
├── service
│ ├── README.md
│ ├── common_service.go
│ ├── relation_service.go
│ ├── service.go
│ ├── sms_service.go
│ ├── sms_service_test.go
│ ├── trans.go
│ ├── user_service.go
│ └── vcode_service.go
├── templates
│ ├── README.md
│ ├── error
│ │ └── 404.html
│ ├── index.html
│ ├── layouts
│ │ ├── footer.html
│ │ ├── header.html
│ │ └── master.html
│ ├── partials
│ │ └── error.html
│ └── user
│ │ ├── login.html
│ │ └── register.html
└── web
│ ├── README.md
│ ├── error.go
│ ├── index.go
│ ├── response.go
│ └── user
│ ├── login.go
│ ├── logout.go
│ ├── register.go
│ └── user.go
├── main.go
├── pkg
├── app
│ ├── app.go
│ ├── config.go
│ ├── doc.go
│ ├── form.go
│ ├── jwt.go
│ ├── options.go
│ └── response.go
├── async
│ └── async.go
├── auth
│ └── auth.go
├── cache
│ ├── README.md
│ ├── cache.go
│ ├── getter.go
│ ├── key.go
│ ├── memory.go
│ ├── memory_test.go
│ ├── metric.go
│ ├── redis.go
│ └── redis_test.go
├── client
│ ├── consulclient
│ │ └── client.go
│ ├── etcdclient
│ │ ├── client.go
│ │ └── client_test.go
│ ├── httpclient
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── options.go
│ │ └── otelhttp.go
│ └── nacosclient
│ │ └── client.go
├── config
│ ├── README.md
│ ├── config.go
│ ├── config_test.go
│ ├── options.go
│ └── testdata
│ │ └── app.yaml
├── container
│ └── group
│ │ ├── example_test.go
│ │ ├── group.go
│ │ └── group_test.go
├── email
│ ├── init.go
│ ├── mail.go
│ ├── smtp.go
│ ├── template.go
│ └── templates
│ │ ├── active-mail.html
│ │ └── reset-mail.html
├── encoding
│ ├── encoding.go
│ ├── encoding_test.go
│ ├── example_test.go
│ ├── gob_encoding.go
│ ├── json
│ │ └── json.go
│ ├── json_encoding.go
│ ├── msgpack_encoding.go
│ └── proto
│ │ └── proto.go
├── errcode
│ ├── README.md
│ ├── code.go
│ ├── error.go
│ └── grpc_error.go
├── flash
│ ├── message.go
│ └── session.go
├── lock
│ ├── etcd.go
│ ├── lock.go
│ ├── lock_test.go
│ ├── luascript.go
│ ├── redis.go
│ ├── redis_test.go
│ └── utils.go
├── log
│ ├── README.md
│ ├── config.go
│ ├── logger.go
│ ├── logger_test.go
│ ├── options.go
│ ├── span_logger.go
│ ├── utils.go
│ └── zap.go
├── metric
│ ├── README.md
│ ├── counter.go
│ ├── counter_test.go
│ ├── gauge.go
│ ├── gauge_test.go
│ ├── golang_app_dashboard.json
│ ├── histogram.go
│ ├── histogram_test.go
│ └── metric.go
├── middleware
│ ├── README.md
│ ├── access_log.go
│ ├── auth.go
│ ├── breaker.go
│ ├── cors.go
│ ├── header.go
│ ├── logging.go
│ ├── metrics.go
│ ├── middleware.go
│ ├── ratelimit.go
│ ├── requestid.go
│ ├── sign_md5.go
│ ├── timeout.go
│ ├── tracing.go
│ └── tracing_test.go
├── queue
│ ├── README.md
│ ├── interface.go
│ ├── kafka
│ │ ├── README.md
│ │ ├── client.go
│ │ ├── config.go
│ │ ├── consumer.go
│ │ ├── consumer_handler.go
│ │ ├── manager.go
│ │ └── producer.go
│ ├── nats
│ │ ├── consumer.go
│ │ ├── init.go
│ │ └── producer.go
│ └── rabbitmq
│ │ ├── README.md
│ │ ├── channel.go
│ │ ├── client.go
│ │ ├── config.go
│ │ ├── connection.go
│ │ ├── consumer.go
│ │ ├── manager.go
│ │ ├── options
│ │ ├── bind.go
│ │ ├── connection.go
│ │ ├── consumer.go
│ │ ├── exchange.go
│ │ ├── publish.go
│ │ └── queue.go
│ │ └── producer.go
├── redis
│ ├── README.md
│ ├── check_repeat.go
│ ├── config.go
│ ├── idalloc.go
│ ├── idalloc_test.go
│ ├── redis.go
│ ├── redis_example_test.go
│ └── redis_test.go
├── registry
│ ├── consul
│ │ ├── client.go
│ │ ├── registry.go
│ │ ├── registry_test.go
│ │ ├── service.go
│ │ └── watcher.go
│ ├── etcd
│ │ ├── registry.go
│ │ ├── registry_test.go
│ │ ├── service.go
│ │ └── watcher.go
│ ├── nacos
│ │ ├── registry.go
│ │ └── watcher.go
│ └── registry.go
├── sign
│ ├── README.md
│ ├── keys.go
│ ├── sign_aes.go
│ ├── sign_hmac.go
│ ├── sign_md5.go
│ ├── sign_rsa.go
│ ├── signer.go
│ ├── singer_test.go
│ ├── verifer.go
│ └── verifer_test.go
├── storage
│ ├── elasticsearch
│ │ ├── README.md
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── document.go
│ │ ├── index.go
│ │ └── search.go
│ ├── mongodb
│ │ └── mongodb.go
│ ├── orm
│ │ ├── log_writer.go
│ │ └── orm.go
│ └── sql
│ │ ├── README.md
│ │ ├── conn.go
│ │ ├── db.go
│ │ ├── metrics.go
│ │ ├── mysql.go
│ │ ├── mysql_test.go
│ │ ├── row.go
│ │ ├── statement.go
│ │ └── transaction.go
├── sync
│ ├── errgroup
│ │ ├── doc.go
│ │ ├── errgroup.go
│ │ ├── errgroup_test.go
│ │ └── example_test.go
│ └── pipeline
│ │ └── fanout
│ │ ├── example_test.go
│ │ ├── fanout.go
│ │ └── fanout_test.go
├── time
│ ├── time.go
│ └── time_test.go
├── trace
│ ├── README.md
│ ├── config.go
│ ├── errors.go
│ ├── jaeger
│ │ └── jaeger.go
│ ├── options.go
│ ├── plugins
│ │ └── function
│ │ │ └── tracer.go
│ └── tracer.go
├── transport
│ ├── consumer
│ │ ├── rabbitmq
│ │ │ └── server.go
│ │ └── redis
│ │ │ ├── options.go
│ │ │ └── server.go
│ ├── crontab
│ │ ├── logger.go
│ │ └── server.go
│ ├── grpc
│ │ ├── client.go
│ │ ├── clientoptions.go
│ │ ├── codec.go
│ │ ├── interceptor.go
│ │ ├── metrics.go
│ │ ├── resolver
│ │ │ ├── direct
│ │ │ │ ├── builder.go
│ │ │ │ └── resolver.go
│ │ │ └── discovery
│ │ │ │ ├── builder.go
│ │ │ │ └── resolver.go
│ │ ├── server.go
│ │ └── server_test.go
│ ├── http
│ │ ├── options.go
│ │ ├── server.go
│ │ ├── server_test.go
│ │ └── status
│ │ │ ├── status.go
│ │ │ └── status_test.go
│ └── transport.go
├── utils
│ ├── README.md
│ ├── debug.go
│ ├── debug_test.go
│ ├── host.go
│ ├── host_test.go
│ ├── ip.go
│ ├── ip_test.go
│ ├── pagination.go
│ ├── slice.go
│ ├── slice_test.go
│ ├── string.go
│ ├── time.go
│ ├── url.go
│ ├── utils.go
│ ├── utils_test.go
│ └── valid.go
└── version
│ ├── base.go
│ ├── doc.go
│ └── version.go
├── scripts
├── README.md
├── admin.sh
└── wrktest.sh
└── test
├── database.sql
├── docker-compose.yaml
├── kafka-docker-compose.yaml
├── nats-docker-compose.yaml
├── rabbitmq-docker-compose.yaml
└── rabbitmq-isolated.conf
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | name: "CodeQL"
7 |
8 | on:
9 | push:
10 | branches: [master]
11 | pull_request:
12 | # The branches below must be a subset of the branches above
13 | branches: [master]
14 |
15 | jobs:
16 | analyze:
17 | name: Analyze
18 | runs-on: ubuntu-latest
19 |
20 | permissions:
21 | # required for all workflows
22 | security-events: write
23 |
24 | strategy:
25 | fail-fast: false
26 | matrix:
27 | # Override automatic language detection by changing the below list
28 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
29 | # TODO: Enable for javascript later
30 | language: ["go"]
31 |
32 | steps:
33 | - name: Checkout repo
34 | uses: actions/checkout@v3
35 |
36 | # Initializes the CodeQL tools for scanning.
37 | - name: Initialize CodeQL
38 | uses: github/codeql-action/init@v2
39 | with:
40 | languages: ${{ matrix.language }}
41 |
42 | - name: Perform CodeQL Analysis
43 | uses: github/codeql-action/analyze@v2
44 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: GoRelease
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*.*.*"
7 |
8 | permissions:
9 | contents: write
10 |
11 | jobs:
12 | release:
13 | name: Release on GitHub
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 0
20 |
21 | - name: Git fetch
22 | run: git fetch --force --tags
23 |
24 | - name: Set up Go
25 | uses: actions/setup-go@v5
26 | with:
27 | go-version: 1.22
28 |
29 | - name: Run GoReleaser
30 | uses: goreleaser/goreleaser-action@v6
31 | with:
32 | distribution: goreleaser
33 | version: "~> v2"
34 | args: release --clean
35 | env:
36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # logs
7 | # log
8 | logs
9 |
10 | # Editors
11 | *~
12 | .*.swp
13 | .#*
14 | \#*#
15 |
16 | # Folders
17 | _obj
18 | _test
19 |
20 | # Architecture specific extensions/prefixes
21 | *.[568vq]
22 | [568vq].out
23 |
24 | *.cgo1.go
25 | *.cgo2.c
26 | _cgo_defun.c
27 | _cgo_gotypes.go
28 | _cgo_export.*
29 |
30 | _testmain.go
31 |
32 | *.exe
33 | *.test
34 | *.prof
35 | *.out
36 | coverage.txt
37 | cover.out
38 | .idea
39 | .idea/*
40 | bin/
41 | .DS_Store
42 |
43 | vendor
44 |
45 | # custom
46 | eagle
47 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | # reference
2 | # 需要安装:https://pre-commit.com/
3 | # - https://blog.xizhibei.me/2019/08/12/the-outpost-of-project-quality-git-hooks/
4 | # - https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
5 | # - https://githooks.com/
6 | - repo: https://github.com/pre-commit/pre-commit-hooks
7 | rev: v3.4.0
8 | hooks:
9 | - id: check-yaml
10 | - repo: git://github.com/dnephin/pre-commit-golang
11 | rev: master
12 | hooks:
13 | - id: go-fmt
14 | - id: go-vet
15 | - id: go-unit-tests
16 | - id: go-build
17 | #- repo: local
18 | # hooks:
19 | # - id: generate-docs
20 | # name: Generate docs
21 | # pass_filenames: false
22 | # entry: make docs
23 | # files: \.go$
24 | # language: system
25 | # types: [text]
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 1024casts
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/cmd/eagle/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/go-eagle/eagle/cmd/eagle
2 |
3 | go 1.22
4 | toolchain go1.24.1
5 |
6 | require (
7 | github.com/AlecAivazis/survey/v2 v2.2.12
8 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
9 | github.com/emicklei/proto v1.9.1
10 | github.com/fatih/color v1.9.0
11 | github.com/jedib0t/go-pretty/v6 v6.2.7
12 | github.com/spf13/cobra v1.1.3
13 | golang.org/x/mod v0.17.0
14 | )
15 |
16 | require (
17 | github.com/inconshreveable/mousetrap v1.0.0 // indirect
18 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
19 | github.com/mattn/go-colorable v0.1.6 // indirect
20 | github.com/mattn/go-isatty v0.0.12 // indirect
21 | github.com/mattn/go-runewidth v0.0.13 // indirect
22 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
23 | github.com/rivo/uniseg v0.2.0 // indirect
24 | github.com/spf13/pflag v1.0.5 // indirect
25 | golang.org/x/crypto v0.35.0 // indirect
26 | golang.org/x/sys v0.30.0 // indirect
27 | golang.org/x/term v0.29.0 // indirect
28 | golang.org/x/text v0.22.0 // indirect
29 | gopkg.in/yaml.v3 v3.0.0 // indirect
30 | )
31 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/base/get.go:
--------------------------------------------------------------------------------
1 | //go:build !go1.16
2 | // +build !go1.16
3 |
4 | package base
5 |
6 | import (
7 | "fmt"
8 | "os"
9 | "os/exec"
10 | )
11 |
12 | // GoInstall go get path.
13 | func GoInstall(path ...string) error {
14 | for _, p := range path {
15 | fmt.Printf("go get -u %s\n", p)
16 | cmd := exec.Command("go", "get", "-u", p)
17 | cmd.Stdout = os.Stdout
18 | cmd.Stderr = os.Stderr
19 | if err := cmd.Run(); err != nil {
20 | return err
21 | }
22 | }
23 |
24 | return nil
25 | }
26 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/base/install.go:
--------------------------------------------------------------------------------
1 | //go:build go1.16
2 | // +build go1.16
3 |
4 | package base
5 |
6 | import (
7 | "fmt"
8 | "os"
9 | "os/exec"
10 | "strings"
11 | )
12 |
13 | // GoInstall go get path.
14 | func GoInstall(path ...string) error {
15 | for _, p := range path {
16 | if !strings.Contains(p, "@") {
17 | p += "@latest"
18 | }
19 | fmt.Printf("go install %s\n", p)
20 | cmd := exec.Command("go", "install", p)
21 | cmd.Stdout = os.Stdout
22 | cmd.Stderr = os.Stderr
23 | if err := cmd.Run(); err != nil {
24 | return err
25 | }
26 | }
27 | return nil
28 | }
29 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/base/mod_test.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import "testing"
4 |
5 | func TestModuleVersion(t *testing.T) {
6 | v, err := ModuleVersion("golang.org/x/mod")
7 | if err != nil {
8 | t.Fatal(err)
9 | }
10 | t.Log(v)
11 | }
12 |
13 | func TestEagleMod(t *testing.T) {
14 | out := EagleMod()
15 | t.Log(out)
16 | }
17 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/base/repo_test.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "context"
5 | "testing"
6 | )
7 |
8 | func TestRepo(t *testing.T) {
9 | r := NewRepo("https://github.com/go-eagle/eagle-layout.git", "main")
10 | if err := r.Clone(context.Background()); err != nil {
11 | t.Fatal(err)
12 | }
13 | if err := r.CopyTo(context.Background(), "/tmp/test_eagle_repo", "github.com/go-eagle/eagle-layout", nil); err != nil {
14 | t.Fatal(err)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/cache/add/add.go:
--------------------------------------------------------------------------------
1 | package add
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/go-eagle/eagle/cmd/eagle/internal/utils"
8 |
9 | "github.com/spf13/cobra"
10 | )
11 |
12 | // CmdCache represents the new command.
13 | var CmdAdd = &cobra.Command{
14 | Use: "add",
15 | Short: "Create a cache file by template",
16 | Long: "Create a cache file using the cache template. Example: eagle cache add UserCache",
17 | Run: run,
18 | }
19 |
20 | var (
21 | targetDir string
22 | )
23 |
24 | func init() {
25 | CmdAdd.Flags().StringVarP(&targetDir, "-target-dir", "t", "internal/dal/cache", "generate target directory")
26 | }
27 |
28 | func run(cmd *cobra.Command, args []string) {
29 | if len(args) == 0 {
30 | fmt.Println("Please enter the cache filename")
31 | return
32 | }
33 | // eg: eagle cache UserCache
34 | filename := args[0]
35 |
36 | c := &Cache{
37 | Name: utils.Ucfirst(filename), // 首字母大写
38 | LcName: utils.Lcfirst(filename), // 首字母小写
39 | UsName: utils.Camel2Case(filename), // 下划线分隔
40 | ColonName: strings.ReplaceAll(utils.Camel2Case(filename), "_", ":"), // 冒号分隔
41 | Path: targetDir,
42 | ModName: utils.ModName(),
43 | }
44 | if err := c.Generate(); err != nil {
45 | fmt.Println(err)
46 | return
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/cache/add/cache.go:
--------------------------------------------------------------------------------
1 | package add
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path"
7 |
8 | "github.com/go-eagle/eagle/cmd/eagle/internal/utils"
9 | )
10 |
11 | // Cache is a cache generator.
12 | type Cache struct {
13 | Name string
14 | LcName string
15 | UsName string
16 | ColonName string
17 | Path string
18 | Service string
19 | Package string
20 | ModName string
21 | }
22 |
23 | // Generate generate a cache template.
24 | func (c *Cache) Generate() error {
25 | body, err := c.execute()
26 | if err != nil {
27 | return err
28 | }
29 | wd, err := os.Getwd()
30 | if err != nil {
31 | panic(err)
32 | }
33 | to := path.Join(wd, c.Path)
34 | if _, err := os.Stat(to); os.IsNotExist(err) {
35 | if err := os.MkdirAll(to, 0700); err != nil {
36 | return err
37 | }
38 | }
39 | name := path.Join(to, utils.Camel2Case(c.Name)+"_cache.go")
40 | if _, err := os.Stat(name); !os.IsNotExist(err) {
41 | return fmt.Errorf("%s already exists", c.Name)
42 | }
43 | return os.WriteFile(name, body, 0644)
44 | }
45 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/cache/cache.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "github.com/go-eagle/eagle/cmd/eagle/internal/cache/add"
5 | "github.com/spf13/cobra"
6 | )
7 |
8 | // CmdProto represents the proto command.
9 | var CmdCache = &cobra.Command{
10 | Use: "cache",
11 | Short: "Generate the cache file",
12 | Long: "Generate the cache file.",
13 | Run: run,
14 | }
15 |
16 | func init() {
17 | CmdCache.AddCommand(add.CmdAdd)
18 | }
19 |
20 | func run(cmd *cobra.Command, args []string) {
21 | }
22 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/handler/add/add.go:
--------------------------------------------------------------------------------
1 | package add
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/cobra"
7 |
8 | "github.com/go-eagle/eagle/cmd/eagle/internal/utils"
9 | )
10 |
11 | // CmdCache represents the new command.
12 | var CmdAdd = &cobra.Command{
13 | Use: "add",
14 | Short: "Create a handler file by template",
15 | Long: "Create a handler file using the handler template. Example: eagle handler add demo",
16 | Run: run,
17 | }
18 |
19 | var (
20 | targetDir string
21 | version string
22 | method string
23 | )
24 |
25 | func init() {
26 | CmdAdd.Flags().StringVarP(&targetDir, "target-dir", "t", "internal/handler", "generate target directory")
27 | CmdAdd.Flags().StringVarP(&version, "version", "v", "v1", "handler version")
28 | CmdAdd.Flags().StringVarP(&method, "method", "m", "GET", "http method, eg: GET, POST, PUT, PATCH, DELETE")
29 | }
30 |
31 | func run(cmd *cobra.Command, args []string) {
32 | if len(args) == 0 {
33 | fmt.Println("Please enter the handler filename")
34 | return
35 | }
36 | // eg: eagle handler add demo
37 | filename := args[0]
38 |
39 | c := &Handler{
40 | Name: utils.Ucfirst(filename), // 首字母大写
41 | LcName: utils.Lcfirst(filename), // 首字母小写
42 | UsName: utils.Camel2Case(filename), // 下划线分隔
43 | Path: targetDir,
44 | Version: version,
45 | Method: method,
46 | }
47 | if err := c.Generate(); err != nil {
48 | fmt.Println(err)
49 | return
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/handler/add/handler.go:
--------------------------------------------------------------------------------
1 | package add
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path"
7 |
8 | "github.com/go-eagle/eagle/cmd/eagle/internal/utils"
9 | )
10 |
11 | // Handler is a cache generator.
12 | type Handler struct {
13 | Name string
14 | LcName string
15 | UsName string
16 | Path string
17 | Version string
18 | Method string
19 | }
20 |
21 | // Generate generate a Handler template.
22 | func (h *Handler) Generate() error {
23 | body, err := h.execute()
24 | if err != nil {
25 | return err
26 | }
27 | wd, err := os.Getwd()
28 | if err != nil {
29 | panic(err)
30 | }
31 | to := path.Join(wd, h.Path, h.Version)
32 | if _, err := os.Stat(to); os.IsNotExist(err) {
33 | if err := os.MkdirAll(to, 0700); err != nil {
34 | return err
35 | }
36 | }
37 | name := path.Join(to, utils.Camel2Case(h.Name)+".go")
38 | if _, err := os.Stat(name); !os.IsNotExist(err) {
39 | return fmt.Errorf("%s already exists", h.Name)
40 | }
41 | return os.WriteFile(name, body, 0644)
42 | }
43 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/handler/handler.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "github.com/go-eagle/eagle/cmd/eagle/internal/handler/add"
5 | "github.com/spf13/cobra"
6 | )
7 |
8 | // CmdHandler represents the handler command.
9 | var CmdHandler = &cobra.Command{
10 | Use: "handler",
11 | Short: "Generate the handler file",
12 | Long: "Generate the handler/controller file.",
13 | Run: run,
14 | }
15 |
16 | func init() {
17 | CmdHandler.AddCommand(add.CmdAdd)
18 | }
19 |
20 | func run(cmd *cobra.Command, args []string) {
21 | }
22 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/proto/add/proto.go:
--------------------------------------------------------------------------------
1 | package add
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path"
7 | )
8 |
9 | // Proto define a proto generator.
10 | type Proto struct {
11 | Name string
12 | Path string
13 | Package string
14 | Service string
15 | GoPackage string
16 | JavaPackage string
17 | }
18 |
19 | // Generate generate a proto template.
20 | func (p *Proto) Generate() error {
21 | body, err := p.execute()
22 | if err != nil {
23 | return err
24 | }
25 | wd, err := os.Getwd()
26 | if err != nil {
27 | panic(err)
28 | }
29 | to := path.Join(wd, p.Path)
30 | if _, err := os.Stat(to); os.IsNotExist(err) {
31 | if err := os.MkdirAll(to, 0700); err != nil {
32 | return err
33 | }
34 | }
35 | name := path.Join(to, p.Name)
36 | if _, err := os.Stat(name); !os.IsNotExist(err) {
37 | return fmt.Errorf("%s already exists", p.Name)
38 | }
39 | return os.WriteFile(name, body, 0644)
40 | }
41 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/proto/add/template.go:
--------------------------------------------------------------------------------
1 | package add
2 |
3 | import (
4 | "bytes"
5 | "html/template"
6 | "strings"
7 | )
8 |
9 | const protoTemplate = `
10 | syntax = "proto3";
11 |
12 | package {{.Package}};
13 |
14 | option go_package = "{{.GoPackage}}";
15 | option java_multiple_files = true;
16 | option java_package = "{{.JavaPackage}}";
17 |
18 | service {{.Service}}Service {
19 | rpc Create{{.Service}} (Create{{.Service}}Request) returns (Create{{.Service}}Reply);
20 | rpc Update{{.Service}} (Update{{.Service}}Request) returns (Update{{.Service}}Reply);
21 | rpc Delete{{.Service}} (Delete{{.Service}}Request) returns (Delete{{.Service}}Reply);
22 | rpc Get{{.Service}} (Get{{.Service}}Request) returns (Get{{.Service}}Reply);
23 | rpc List{{.Service}} (List{{.Service}}Request) returns (List{{.Service}}Reply);
24 | }
25 |
26 | message Create{{.Service}}Request {}
27 | message Create{{.Service}}Reply {}
28 |
29 | message Update{{.Service}}Request {}
30 | message Update{{.Service}}Reply {}
31 |
32 | message Delete{{.Service}}Request {}
33 | message Delete{{.Service}}Reply {}
34 |
35 | message Get{{.Service}}Request {}
36 | message Get{{.Service}}Reply {}
37 |
38 | message List{{.Service}}Request {}
39 | message List{{.Service}}Reply {}
40 | `
41 |
42 | func (p *Proto) execute() ([]byte, error) {
43 | buf := new(bytes.Buffer)
44 | tmpl, err := template.New("proto").Parse(strings.TrimSpace(protoTemplate))
45 | if err != nil {
46 | return nil, err
47 | }
48 | if err := tmpl.Execute(buf, p); err != nil {
49 | return nil, err
50 | }
51 | return buf.Bytes(), nil
52 | }
53 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/proto/proto.go:
--------------------------------------------------------------------------------
1 | package proto
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 |
6 | "github.com/go-eagle/eagle/cmd/eagle/internal/proto/add"
7 | "github.com/go-eagle/eagle/cmd/eagle/internal/proto/client"
8 | "github.com/go-eagle/eagle/cmd/eagle/internal/proto/server"
9 | )
10 |
11 | // CmdProto represents the proto command.
12 | var CmdProto = &cobra.Command{
13 | Use: "proto",
14 | Short: "Generate the proto files",
15 | Long: "Generate the proto files.",
16 | Run: run,
17 | }
18 |
19 | func init() {
20 | CmdProto.AddCommand(add.CmdAdd)
21 | CmdProto.AddCommand(client.CmdClient)
22 | CmdProto.AddCommand(server.CmdServer)
23 | }
24 |
25 | func run(cmd *cobra.Command, args []string) {
26 | }
27 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/repo/add/add.go:
--------------------------------------------------------------------------------
1 | package add
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/go-eagle/eagle/cmd/eagle/internal/utils"
7 |
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | // CmdAdd represents the new command.
12 | var CmdAdd = &cobra.Command{
13 | Use: "add",
14 | Short: "Create a repo file by template",
15 | Long: "Create a repo file using the repo template. Example: eagle repo add UserCache",
16 | Run: run,
17 | }
18 |
19 | var (
20 | targetDir string
21 | withCache bool
22 | )
23 |
24 | func init() {
25 | CmdAdd.Flags().StringVarP(&targetDir, "-target-dir", "t", "internal/repository", "generate target directory")
26 | CmdAdd.Flags().BoolVarP(&withCache, "-with-cache", "c", true, "add cache operate")
27 | }
28 |
29 | func run(cmd *cobra.Command, args []string) {
30 | if len(args) == 0 {
31 | fmt.Println("Please enter the repo filename")
32 | return
33 | }
34 | // eg: eagle cache UserCache
35 | filename := args[0]
36 |
37 | c := &Repo{
38 | Name: utils.Ucfirst(filename), // 首字母大写
39 | LcName: utils.Lcfirst(filename), // 首字母小写
40 | UsName: utils.Camel2Case(filename), // 下划线分隔
41 | Path: targetDir,
42 | ModName: utils.ModName(),
43 | WithCache: withCache,
44 | }
45 | if err := c.Generate(); err != nil {
46 | fmt.Println(err)
47 | return
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/repo/add/repo.go:
--------------------------------------------------------------------------------
1 | package add
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path"
7 |
8 | "github.com/go-eagle/eagle/cmd/eagle/internal/utils"
9 | )
10 |
11 | // Repo is a cache generator.
12 | type Repo struct {
13 | Name string
14 | LcName string
15 | UsName string
16 | Path string
17 | Service string
18 | Package string
19 | ModName string
20 | WithCache bool
21 | }
22 |
23 | // Generate generate a repo template.
24 | func (r *Repo) Generate() error {
25 | body, err := r.execute()
26 | if err != nil {
27 | return err
28 | }
29 | wd, err := os.Getwd()
30 | if err != nil {
31 | panic(err)
32 | }
33 | to := path.Join(wd, r.Path)
34 | if _, err := os.Stat(to); os.IsNotExist(err) {
35 | if err := os.MkdirAll(to, 0700); err != nil {
36 | return err
37 | }
38 | }
39 | name := path.Join(to, utils.Camel2Case(r.Name)+"_repo.go")
40 | if _, err := os.Stat(name); !os.IsNotExist(err) {
41 | return fmt.Errorf("%s already exists", r.Name)
42 | }
43 | return os.WriteFile(name, body, 0644)
44 | }
45 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/repo/repo.go:
--------------------------------------------------------------------------------
1 | package repo
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 |
6 | "github.com/go-eagle/eagle/cmd/eagle/internal/repo/add"
7 | )
8 |
9 | // CmdProto represents the proto command.
10 | var CmdRepo = &cobra.Command{
11 | Use: "repo",
12 | Short: "Generate the repo file",
13 | Long: "Generate the repo file.",
14 | Run: run,
15 | }
16 |
17 | func init() {
18 | CmdRepo.AddCommand(add.CmdAdd)
19 | }
20 |
21 | func run(cmd *cobra.Command, args []string) {
22 | }
23 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/service/add/add.go:
--------------------------------------------------------------------------------
1 | package add
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/go-eagle/eagle/cmd/eagle/internal/utils"
7 |
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | // CmdAdd represents the new command.
12 | var CmdAdd = &cobra.Command{
13 | Use: "add",
14 | Short: "Create a svc file by template",
15 | Long: "Create a svc file using the svc template. Example: eagle svc add User",
16 | Run: run,
17 | }
18 |
19 | var (
20 | targetDir string
21 | )
22 |
23 | func init() {
24 | CmdAdd.Flags().StringVarP(&targetDir, "-target-dir", "t", "internal/service", "generate target directory")
25 | }
26 |
27 | func run(cmd *cobra.Command, args []string) {
28 | if len(args) == 0 {
29 | fmt.Println("Please enter the cache filename")
30 | return
31 | }
32 | // eg: eagle service add User
33 | filename := args[0]
34 |
35 | c := &Service{
36 | Name: utils.Ucfirst(filename), // 首字母大写
37 | LcName: utils.Lcfirst(filename),
38 | Path: targetDir,
39 | ModName: utils.ModName(),
40 | }
41 | if err := c.Generate(); err != nil {
42 | fmt.Println(err)
43 | return
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/service/add/service.go:
--------------------------------------------------------------------------------
1 | package add
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path"
7 |
8 | "github.com/go-eagle/eagle/cmd/eagle/internal/utils"
9 | )
10 |
11 | // Service is a svc generator.
12 | type Service struct {
13 | Name string
14 | LcName string
15 | Path string
16 | Service string
17 | Package string
18 | ModName string
19 | }
20 |
21 | // Generate generate a svc template.
22 | func (s *Service) Generate() error {
23 | body, err := s.execute()
24 | if err != nil {
25 | return err
26 | }
27 | wd, err := os.Getwd()
28 | if err != nil {
29 | panic(err)
30 | }
31 | to := path.Join(wd, s.Path)
32 | if _, err := os.Stat(to); os.IsNotExist(err) {
33 | if err := os.MkdirAll(to, 0700); err != nil {
34 | return err
35 | }
36 | }
37 | name := path.Join(to, utils.Camel2Case(s.Name)+"_svc.go")
38 | if _, err := os.Stat(name); !os.IsNotExist(err) {
39 | return fmt.Errorf("%s already exists", s.Name)
40 | }
41 | return os.WriteFile(name, body, 0644)
42 | }
43 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/service/add/template.go:
--------------------------------------------------------------------------------
1 | package add
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 |
7 | "github.com/alecthomas/template"
8 | )
9 |
10 | var svcTemplate = `
11 | package service
12 |
13 | import (
14 | "context"
15 |
16 | "{{.ModName}}/internal/repository"
17 | )
18 |
19 | // {{.Name}}Service define a interface
20 | type {{.Name}}Service interface {
21 | Hello(ctx context.Context) error
22 | }
23 |
24 | type {{.LcName}}Service struct {
25 | repo repository.{{.Name}}Repo
26 | }
27 |
28 | var _ {{.Name}}Service = (*{{.LcName}}Service)(nil)
29 |
30 | func New{{.Name}}Service(repo repository.{{.Name}}Repo) {{.Name}}Service {
31 | return &{{.LcName}}Service{
32 | repo: repo,
33 | }
34 | }
35 |
36 | // Hello .
37 | func (s *{{.LcName}}Service) Hello(ctx context.Context) error {
38 | return nil
39 | }
40 | `
41 |
42 | func (s *Service) execute() ([]byte, error) {
43 | buf := new(bytes.Buffer)
44 | tmpl, err := template.New("service").Parse(strings.TrimSpace(svcTemplate))
45 | if err != nil {
46 | return nil, err
47 | }
48 | if err := tmpl.Execute(buf, s); err != nil {
49 | return nil, err
50 | }
51 | return buf.Bytes(), nil
52 | }
53 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/service/service.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 |
6 | "github.com/go-eagle/eagle/cmd/eagle/internal/service/add"
7 | )
8 |
9 | // CmdService represents the service command.
10 | var CmdService = &cobra.Command{
11 | Use: "svc",
12 | Aliases: []string{"service"},
13 | Short: "Generate the service/svc file",
14 | Long: "Generate the service/svc file.",
15 | Run: run,
16 | }
17 |
18 | func init() {
19 | CmdService.AddCommand(add.CmdAdd)
20 | }
21 |
22 | func run(cmd *cobra.Command, args []string) {
23 | }
24 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/task/add/add.go:
--------------------------------------------------------------------------------
1 | package add
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/fatih/color"
8 |
9 | "github.com/go-eagle/eagle/cmd/eagle/internal/utils"
10 |
11 | "github.com/spf13/cobra"
12 | )
13 |
14 | // CmdAdd represents the new command.
15 | var CmdAdd = &cobra.Command{
16 | Use: "add",
17 | Short: "Create a task file by template",
18 | Long: "Create a task file using the task template. Example: eagle task add EmailWelcome",
19 | Run: run,
20 | }
21 |
22 | var (
23 | targetDir string
24 | )
25 |
26 | func init() {
27 | CmdAdd.Flags().StringVarP(&targetDir, "-target-dir", "t", "internal/tasks", "generate target directory")
28 | }
29 |
30 | func run(cmd *cobra.Command, args []string) {
31 | if len(args) == 0 {
32 | fmt.Printf("Please enter the %s\n", color.RedString("task name"))
33 | return
34 | }
35 | // eg: eagle task add EmailWelcome
36 | filename := args[0]
37 |
38 | c := &Task{
39 | Name: utils.Ucfirst(filename), // 首字母大写
40 | LcName: utils.Lcfirst(filename), // 首字母小写
41 | UsName: utils.Camel2Case(filename), // 下划线分隔
42 | ColonName: strings.ReplaceAll(utils.Camel2Case(filename), "_", ":"), // 冒号分隔
43 | Path: targetDir,
44 | ModName: utils.ModName(),
45 | }
46 | if err := c.Generate(); err != nil {
47 | fmt.Println(err)
48 | return
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/task/add/task.go:
--------------------------------------------------------------------------------
1 | package add
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | "path"
8 |
9 | "github.com/go-eagle/eagle/cmd/eagle/internal/utils"
10 | )
11 |
12 | // Repo is a cache generator.
13 | type Task struct {
14 | Name string
15 | LcName string
16 | UsName string
17 | ColonName string
18 | Path string
19 | Service string
20 | Package string
21 | ModName string
22 | WithCache bool
23 | }
24 |
25 | // Generate generate a repo template.
26 | func (t *Task) Generate() error {
27 | body, err := t.execute()
28 | if err != nil {
29 | return err
30 | }
31 | wd, err := os.Getwd()
32 | if err != nil {
33 | panic(err)
34 | }
35 | to := path.Join(wd, t.Path)
36 | if _, err := os.Stat(to); os.IsNotExist(err) {
37 | if err := os.MkdirAll(to, 0700); err != nil {
38 | return err
39 | }
40 | }
41 | name := path.Join(to, utils.Camel2Case(t.Name)+".go")
42 | if _, err := os.Stat(name); !os.IsNotExist(err) {
43 | return fmt.Errorf("%s already exists", t.Name)
44 | }
45 | return ioutil.WriteFile(name, body, 0644)
46 | }
47 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/task/task.go:
--------------------------------------------------------------------------------
1 | package task
2 |
3 | import (
4 | "github.com/go-eagle/eagle/cmd/eagle/internal/task/list"
5 | "github.com/spf13/cobra"
6 |
7 | "github.com/go-eagle/eagle/cmd/eagle/internal/task/add"
8 | )
9 |
10 | // CmdProto represents the proto command.
11 | var CmdTask = &cobra.Command{
12 | Use: "task",
13 | Short: "Generate the task file",
14 | Long: "Generate the task file.",
15 | Run: run,
16 | }
17 |
18 | func init() {
19 | CmdTask.AddCommand(add.CmdAdd)
20 | CmdTask.AddCommand(list.CmdList)
21 | }
22 |
23 | func run(cmd *cobra.Command, args []string) {
24 | }
25 |
--------------------------------------------------------------------------------
/cmd/eagle/internal/upgrade/upgrade.go:
--------------------------------------------------------------------------------
1 | package upgrade
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/cobra"
7 |
8 | "github.com/go-eagle/eagle/cmd/eagle/internal/base"
9 | )
10 |
11 | // CmdUpgrade represents the upgrade command.
12 | var CmdUpgrade = &cobra.Command{
13 | Use: "upgrade",
14 | Short: "Upgrade the eagle tools",
15 | Long: "Upgrade the eagle tools. Example: eagle upgrade",
16 | Run: Run,
17 | }
18 |
19 | // Run upgrade the eagle tools.
20 | func Run(cmd *cobra.Command, args []string) {
21 | err := base.GoInstall(
22 | "github.com/go-eagle/eagle/cmd/eagle",
23 | "github.com/go-eagle/eagle/cmd/protoc-gen-go-gin",
24 | "google.golang.org/protobuf/cmd/protoc-gen-go",
25 | "google.golang.org/grpc/cmd/protoc-gen-go-grpc",
26 | "github.com/envoyproxy/protoc-gen-validate",
27 | "github.com/google/gnostic",
28 | "github.com/google/gnostic/cmd/protoc-gen-openapi",
29 | "github.com/google/wire/cmd/wire",
30 | )
31 | if err != nil {
32 | fmt.Println(err)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/cmd/eagle/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/spf13/cobra"
7 |
8 | "github.com/go-eagle/eagle/cmd/eagle/internal/cache"
9 | "github.com/go-eagle/eagle/cmd/eagle/internal/handler"
10 | "github.com/go-eagle/eagle/cmd/eagle/internal/model"
11 | "github.com/go-eagle/eagle/cmd/eagle/internal/project"
12 | "github.com/go-eagle/eagle/cmd/eagle/internal/proto"
13 | "github.com/go-eagle/eagle/cmd/eagle/internal/repo"
14 | "github.com/go-eagle/eagle/cmd/eagle/internal/run"
15 | "github.com/go-eagle/eagle/cmd/eagle/internal/service"
16 | "github.com/go-eagle/eagle/cmd/eagle/internal/task"
17 | "github.com/go-eagle/eagle/cmd/eagle/internal/upgrade"
18 | )
19 |
20 | var (
21 | // Version is the version of the compiled software.
22 | Version = "v1.0.3"
23 |
24 | rootCmd = &cobra.Command{
25 | Use: "eagle",
26 | Short: "Eagle: A microservice framework for Go",
27 | Long: `Eagle: A microservice framework for Go`,
28 | Version: Version,
29 | }
30 | )
31 |
32 | func init() {
33 | rootCmd.AddCommand(project.CmdNew)
34 | rootCmd.AddCommand(run.CmdRun)
35 | rootCmd.AddCommand(handler.CmdHandler)
36 | rootCmd.AddCommand(cache.CmdCache)
37 | rootCmd.AddCommand(repo.CmdRepo)
38 | rootCmd.AddCommand(service.CmdService)
39 | rootCmd.AddCommand(proto.CmdProto)
40 | rootCmd.AddCommand(task.CmdTask)
41 | rootCmd.AddCommand(model.CmdNew)
42 | rootCmd.AddCommand(upgrade.CmdUpgrade)
43 | }
44 |
45 | func main() {
46 | if err := rootCmd.Execute(); err != nil {
47 | log.Fatal(err)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/cmd/protoc-gen-go-gin/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/go-eagle/eagle/cmd/protoc-gen-go-gin
2 |
3 | go 1.16
4 |
5 | require (
6 | google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb
7 | google.golang.org/protobuf v1.27.1
8 | )
9 |
--------------------------------------------------------------------------------
/cmd/protoc-gen-go-gin/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 |
7 | "google.golang.org/protobuf/compiler/protogen"
8 | "google.golang.org/protobuf/types/pluginpb"
9 | )
10 |
11 | const (
12 | version = "0.0.14"
13 | )
14 |
15 | var (
16 | showVersion = flag.Bool("version", false, "print the version and exit")
17 | )
18 |
19 | func main() {
20 | flag.Parse()
21 | if *showVersion {
22 | fmt.Printf("protoc-gen-go-gin %v\n", version)
23 | return
24 | }
25 | protogen.Options{
26 | ParamFunc: flag.CommandLine.Set,
27 | }.Run(func(gen *protogen.Plugin) error {
28 | gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
29 | for _, f := range gen.Files {
30 | if !f.Generate {
31 | continue
32 | }
33 | generateFile(gen, f)
34 | }
35 | return nil
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/config/docker/README.md:
--------------------------------------------------------------------------------
1 | # conf
2 |
3 | 默认从 config.local.yaml 加载配置, 可以通过下面命令生成:
4 |
5 | ```bash
6 | cp config.sample.yaml config.local.yaml
7 | ```
8 |
9 | 如果是多环境可以设定不同的配置文件,比如:
10 | - config.local.yaml 本地开发环境
11 | - config.dev.yaml 开发环境
12 | - config.test.yaml 测试环境
13 | - config.pre.yaml 预发布环境
14 | - config.prod.yaml 线上环境
15 |
16 | 生成开发环境配置文件:
17 |
18 | ```bash
19 | cp config.sample.yaml config.dev.yaml
20 | ```
21 |
22 | 以上配置是可以提交到Git等代码仓库的,`config.local.yaml` 配置文件默认不会被提交。
23 |
24 | 使用本地配置文件来运行程序,命令如下:
25 |
26 | ```bash
27 | # 本地启动
28 | # 也可以直接 ./eagle
29 | ./eagle -c config/config.local.yaml
30 | # 开发环境启动
31 | ./eagle -c config/config.dev.yaml
32 | # 线上环境启动
33 | ./eagle -c config/config.prod.yaml
34 |
35 | ```
36 |
--------------------------------------------------------------------------------
/config/docker/app.yaml:
--------------------------------------------------------------------------------
1 | Name: eagle
2 | Version: 1.0.0
3 | PprofPort: :5555
4 | Mode: debug # debug, release, test
5 | JwtSecret: JWT_SECRET
6 | JwtTimeout: 86400
7 | CookieName: jwt-token
8 | SSL: true
9 | CtxDefaultTimeout: 12
10 | CSRF: true
11 | Debug: false
12 | EnableTrace: false
13 | EnablePprof: true
14 |
15 | HTTP:
16 | Addr: :8080
17 | ReadTimeout: 3s
18 | WriteTimeout: 3s
19 | GRPC:
20 | Addr: :9090
21 | ReadTimeout: 5s
22 | WriteTimeout: 5s
--------------------------------------------------------------------------------
/config/docker/database.yaml:
--------------------------------------------------------------------------------
1 | default:
2 | Driver: mysql # 驱动名称,目前支持 mysql,postgres,默认: mysql
3 | Name: eagle # 数据库名称
4 | Addr: db:3306 # 如果是 docker,可以替换为 对应的服务名称,eg: db:3306
5 | UserName: root
6 | Password: root
7 | ShowLog: true # 是否打印所有SQL日志
8 | MaxIdleConn: 10 # 最大闲置的连接数,0意味着使用默认的大小2, 小于0表示不使用连接池
9 | MaxOpenConn: 60 # 最大打开的连接数, 需要小于数据库配置中的max_connections数
10 | Timeout: 3s # 数据库连接超时时间
11 | ReadTimeout: 3s # 数据库去读超时时间, 0代表不限制
12 | WriteTimeout: 3s # 数据库写入超时时间, 0代表不限制
13 | ConnMaxLifeTime: 4h # 单个连接最大存活时间,建议设置比数据库超时时长(wait_timeout)稍小一些
14 | SlowThreshold: 500ms # 慢查询阈值,设置后只打印慢查询日志,默认为200ms
15 | EnableTrace: false
16 |
--------------------------------------------------------------------------------
/config/docker/logger.yaml:
--------------------------------------------------------------------------------
1 | Development: false
2 | DisableCaller: false
3 | DisableStacktrace: false
4 | Encoding: json # json or console
5 | Level: info # 日志级别,INFO, WARN, ERROR
6 | ServiceName: user-service
7 | Filename: eagle
8 | Writers: console # 有2个可选项:file,console 选择file会将日志记录到logger_file指定的日志文件中,选择console会将日志输出到标准输出,当然也可以两者同时选择
9 | LoggerDir: /data/logs
10 | LogRollingPolicy: daily
11 | LogRotateDate: 1
12 | LogRotateSize: 1
13 | LogBackupCount: 7
14 | FlushInterval: 1s
15 |
--------------------------------------------------------------------------------
/config/docker/nginx_api.conf:
--------------------------------------------------------------------------------
1 | upstream web {
2 | server app:8080;
3 | }
4 |
5 | server {
6 | listen 80;
7 | server_name localhost;
8 |
9 | location / {
10 | # https://stackoverflow.com/questions/42720618/docker-nginx-stopped-emerg-11-host-not-found-in-upstream
11 | resolver 127.0.0.1;
12 |
13 | proxy_set_header Host $http_host;
14 | proxy_set_header X-Forwarded-Host $http_host;
15 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
16 | proxy_set_header X-Real-IP $remote_addr;
17 |
18 | client_max_body_size 5m;
19 |
20 | proxy_pass http://web;
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/config/docker/redis.yaml:
--------------------------------------------------------------------------------
1 | default:
2 | Addr: redis:6379
3 | Password: ""
4 | DB: 0
5 | MinIdleConn: 200
6 | DialTimeout: 60s
7 | ReadTimeout: 500ms
8 | WriteTimeout: 500ms
9 | PoolSize: 100
10 | PoolTimeout: 240s
11 | EnableTrace: true
--------------------------------------------------------------------------------
/config/docker/trace.yaml:
--------------------------------------------------------------------------------
1 | ServiceName: "eagle"
2 | LocalAgentHostPort: "127.0.0.1:6831"
3 | CollectorEndpoint: "http://localhost:14268/api/traces"
--------------------------------------------------------------------------------
/config/local/README.md:
--------------------------------------------------------------------------------
1 | # conf
2 |
3 | 默认从 config/{ENV} 加载配置, 可以通过下面命令生成:
4 |
5 | 如果是多环境可以设定不同的配置目录,比如:
6 | - config/local 本地开发环境
7 | - config/test 测试环境
8 | - config/staging 预发布环境
9 | - config/prod 线上环境
10 |
11 | > 环境命名参考自:https://cloud.google.com/apis/design/directory_structure
12 |
13 | 生成测试环境配置文件:
14 |
15 | ```bash
16 | cp -r config/localhost config/test
17 | ```
18 |
19 | 使用本地配置文件来运行程序,命令如下:
20 |
21 | ```bash
22 | # 本地启动
23 | # 也可以直接 APP_ENV={env} ./eagle
24 | APP_ENV=local ./eagle -c config
25 | 或
26 | ./eagle -e local -c config
27 | ```
28 |
--------------------------------------------------------------------------------
/config/local/app.yaml:
--------------------------------------------------------------------------------
1 | Name: eagle
2 | Version: 1.0.0
3 | PprofPort: :5555
4 | Mode: debug # debug, release, test
5 | JwtSecret: JWT_SECRET
6 | JwtTimeout: 86400
7 | CookieName: jwt-token
8 | SSL: true
9 | CtxDefaultTimeout: 12
10 | CSRF: true
11 | Debug: false
12 | EnableTrace: false
13 | EnablePprof: true
14 |
15 | HTTP:
16 | Addr: :8080
17 | ReadTimeout: 3s
18 | WriteTimeout: 3s
19 | GRPC:
20 | Addr: :9090
21 | ReadTimeout: 5s
22 | WriteTimeout: 5s
--------------------------------------------------------------------------------
/config/local/database.yaml:
--------------------------------------------------------------------------------
1 | default:
2 | Driver: mysql # 驱动名称,目前支持: mysql,postgres,默认: mysql
3 | Name: eagle # 数据库名称
4 | Addr: localhost:3306 # 如果是 docker,可以替换为 对应的服务名称,eg: db:3306, pg:5432
5 | UserName: root
6 | Password: 123456
7 | ShowLog: true # 是否打印所有SQL日志
8 | MaxIdleConn: 10 # 最大闲置的连接数,0意味着使用默认的大小2, 小于0表示不使用连接池
9 | MaxOpenConn: 60 # 最大打开的连接数, 需要小于数据库配置中的max_connections数
10 | Timeout: 3s # 数据库连接超时时间, 如果是 PostgreSQL 不需要加入单位
11 | ReadTimeout: 3s # 数据库去读超时时间, 0代表不限制,如果是PostgreSQL, 3000代表3s
12 | WriteTimeout: 3s # 数据库写入超时时间, 0代表不限制,如果是PostgreSQL, 不会使用该字段的值
13 | ConnMaxLifeTime: 4h # 单个连接最大存活时间,建议设置比数据库超时时长(wait_timeout)稍小一些
14 | SlowThreshold: 500ms # 慢查询阈值,设置后只打印慢查询日志,默认为200ms
15 | EnableTrace: false
16 | user:
17 | Driver: mysql # 驱动名称,目前支持: mysql,postgres,默认: mysql
18 | Name: eagle # 数据库名称
19 | Addr: localhost:3306 # 如果是 docker,可以替换为 对应的服务名称,eg: db:3306, pg:5432
20 | UserName: root
21 | Password: 123456
22 | ShowLog: true # 是否打印所有SQL日志
23 | MaxIdleConn: 10 # 最大闲置的连接数,0意味着使用默认的大小2, 小于0表示不使用连接池
24 | MaxOpenConn: 60 # 最大打开的连接数, 需要小于数据库配置中的max_connections数
25 | Timeout: 3s # 数据库连接超时时间
26 | ReadTimeout: 3s # 数据库去读超时时间, 0代表不限制
27 | WriteTimeout: 3s # 数据库写入超时时间, 0代表不限制
28 | ConnMaxLifeTime: 4h # 单个连接最大存活时间,建议设置比数据库超时时长(wait_timeout)稍小一些
29 | SlowThreshold: 500ms # 慢查询阈值,设置后只打印慢查询日志,默认为200ms
30 | EnableTrace: false
31 |
--------------------------------------------------------------------------------
/config/local/kafka.yaml:
--------------------------------------------------------------------------------
1 | default: # 实例名称
2 | Version: "2.8.1"
3 | RequiredAcks: 1
4 | Topic: "test-topic"
5 | ConsumeTopic:
6 | - "test-topic1"
7 | - "test-topic2"
8 | Brokers:
9 | - "localhost:9092"
10 | GroupID: "test-group"
11 | Partitioner: "hash"
12 |
13 | order: # 另一个实例配置
14 | Version: "2.8.1"
15 | RequiredAcks: 1
16 | Topic: "order-topic"
17 | ConsumeTopic:
18 | - "order-topic"
19 | Brokers:
20 | - "localhost:9092"
21 | GroupID: "order-group"
22 | Partitioner: "random"
23 |
--------------------------------------------------------------------------------
/config/local/logger.yaml:
--------------------------------------------------------------------------------
1 | Development: false
2 | DisableCaller: false
3 | DisableStacktrace: false
4 | Encoding: json # json or console
5 | Level: info # 日志级别,INFO, WARN, ERROR
6 | ServiceName: user-service
7 | Filename: eagle
8 | Writers: console # 有2个可选项:file,console 选择file会将日志记录到logger_file指定的日志文件中,选择console会将日志输出到标准输出,当然也可以两者同时选择
9 | LoggerDir: /data/logs
10 | LogRollingPolicy: daily
11 | LogRotateDate: 1
12 | LogRotateSize: 1
13 | LogBackupCount: 7
14 | FlushInterval: 1s
15 |
--------------------------------------------------------------------------------
/config/local/nginx_api.conf:
--------------------------------------------------------------------------------
1 | upstream web {
2 | server app:8080;
3 | }
4 |
5 | server {
6 | listen 80;
7 | server_name localhost;
8 |
9 | location / {
10 | # https://stackoverflow.com/questions/42720618/docker-nginx-stopped-emerg-11-host-not-found-in-upstream
11 | resolver 127.0.0.1;
12 |
13 | proxy_set_header Host $http_host;
14 | proxy_set_header X-Forwarded-Host $http_host;
15 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
16 | proxy_set_header X-Real-IP $remote_addr;
17 |
18 | client_max_body_size 5m;
19 |
20 | proxy_pass http://web;
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/config/local/rabbitmq.yaml:
--------------------------------------------------------------------------------
1 | test-demo:
2 | Uri: "amqp://guest:guest@localhost:5672/"
3 | AutoDeclare: true
4 | Timeout: 5s
5 | Exchange:
6 | Name: local-test-exchange
7 | Kind: direct
8 | Durable: true
9 | AutoDelete: false
10 | Internal: false
11 | NoWait: false
12 | Args: {}
13 | Queue:
14 | name: local-test-queue
15 | durable: true
16 | AutoDelete: false
17 | Exclusive: false
18 | NoWait: false
19 | Args: {}
20 | Bind:
21 | RoutingKey: local-test-routing-key
22 | NoWait: false
23 | Args: {}
--------------------------------------------------------------------------------
/config/local/redis.yaml:
--------------------------------------------------------------------------------
1 | default:
2 | Addr: 127.0.0.1:6379
3 | Password: ""
4 | DB: 0
5 | MinIdleConn: 200
6 | DialTimeout: 60s
7 | ReadTimeout: 500ms
8 | WriteTimeout: 500ms
9 | PoolSize: 100
10 | PoolTimeout: 240s
11 | EnableTrace: true
--------------------------------------------------------------------------------
/config/local/trace.yaml:
--------------------------------------------------------------------------------
1 | ServiceName: "eagle"
2 | LocalAgentHostPort: "127.0.0.1:6831"
3 | CollectorEndpoint: "http://localhost:14268/api/traces"
--------------------------------------------------------------------------------
/deploy/README.md:
--------------------------------------------------------------------------------
1 | ## 部署
2 |
3 | 主要存放一些部署相关的配置文件和脚本
4 |
5 | ### 部署Go应用
6 |
7 | > see: https://eddycjy.com/posts/kubernetes/2020-05-03-deployment/
8 |
9 |
10 | ## 监控
11 |
12 | 包含对机器(node或者container)的监控、应用的监控、数据库的监控等。
13 |
14 | 使用 `docker-compose` 可以在本地一键部署,配置如下:
15 |
16 | ```yaml
17 |
18 | ```
19 |
20 | ## 配置etcd
21 |
22 | create a namespace
23 |
24 | `$ kubectl create namespace etcd`
25 |
26 | create the service
27 |
28 | `$ kubectl apply -f etcd-service.yaml -n etcd`
29 |
30 | create the cluster(statefulSet)
31 |
32 | `$ cat etcd.yml.tmpl | etcd_config.bash | kubectl apply -n etcd -f -`
33 |
34 | Verify the cluster's health
35 |
36 | `$ kubectl exec -it etcd-0 -n etcd etcdctl cluster-health`
37 |
38 | The cluster is exposed through minikube's IP
39 |
40 | ```bash
41 | $ IP=$(minikube ip)
42 | $ PORT=$(kubectl get services -o jsonpath="{.spec.ports[].nodePort}" etcd-client -n etcd)
43 | $ etcdctl --endpoints http://${IP}:${PORT} get foo
44 | ```
45 |
46 | Destroy the services
47 |
48 | ```bash
49 | $ kubectl delete services,statefulsets --all -n etcd
50 | ```
51 |
52 | 给Etcd集群做个Web UI
53 |
54 | ```
55 | $ kubectl apply -f etcd-ui-configmap.yaml
56 | $ kubectl apply -f etcd-ui.yaml
57 | ```
58 |
59 | > Ref: https://github.com/kevinyan815/LearningKubernetes/tree/master/e3w
60 |
61 | > 参考:
62 | > https://mp.weixin.qq.com/s/AkIvkW22dvqcdFXkiTpv8Q
63 | > https://github.com/kevinyan815/LearningKubernetes/tree/master/etcd
--------------------------------------------------------------------------------
/deploy/grafana/alertmanager.yml:
--------------------------------------------------------------------------------
1 | #global config
2 | global:
3 | resolve_timeout: 30s
4 | slack_api_url: ''
5 | route:
6 | receiver: 'slack-notifications'
7 |
8 | receivers:
9 | - name: 'slack-notifications'
10 | slack_configs:
11 | - channel: '#alerts'
12 | send_resolved: true
--------------------------------------------------------------------------------
/deploy/grafana/rules.yml:
--------------------------------------------------------------------------------
1 | groups:
2 | - name: Rules
3 | rules:
4 | - alert: InstanceDown
5 | # Condition for alerting
6 | expr: up == 0
7 | for: 1m
8 | # Annotation - additional informational labels to store more information
9 | annotations:
10 | title: 'Instance {{ $labels.instance }} down'
11 | description: '{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 1 minute.'
12 | # Labels - additional labels to be attached to the alert
13 | labels:
14 | severity: 'critical'
--------------------------------------------------------------------------------
/deploy/k8s/aws-auth.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: aws-auth
5 | namespace: kube-system
6 | data:
7 | # userarn 需要修改为自己的
8 | mapUsers: |
9 | - userarn: arn:aws:iam::111222333:user/github-ci
10 | username: github-ci
11 | groups:
12 | - system:masters
--------------------------------------------------------------------------------
/deploy/k8s/consul-server-http.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: consul-server-http
5 | spec:
6 | selector:
7 | name: consul-server
8 | type: NodePort
9 | ports:
10 | - protocol: TCP
11 | port: 8500
12 | targetPort: 8500
13 | nodePort: 30098
14 | name: consul-server-tcp
--------------------------------------------------------------------------------
/deploy/k8s/consul-server-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: consul-server
5 | labels:
6 | name: consul-server
7 | spec:
8 | selector:
9 | name: consul-server
10 | ports:
11 | - name: http
12 | port: 8500
13 | targetPort: 8500
14 | - name: https
15 | port: 8443
16 | targetPort: 8443
17 | - name: rpc
18 | port: 8400
19 | targetPort: 8400
--------------------------------------------------------------------------------
/deploy/k8s/etcd-service.yaml:
--------------------------------------------------------------------------------
1 | # https://sgotti.me/post/kubernetes-persistent-etcd/
2 | ---
3 | apiVersion: v1
4 | kind: Service
5 | metadata:
6 | name: etcd
7 | namespace: etcd
8 | annotations:
9 | # Create endpoints also if the related pod isn't ready
10 | service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
11 | spec:
12 | ports:
13 | - port: 2379
14 | name: client
15 | - port: 2380
16 | name: peer
17 | clusterIP: None
18 | selector:
19 | component: etcd
20 | ---
21 | apiVersion: v1
22 | kind: Service
23 | metadata:
24 | name: etcd-client
25 | namespace: etcd
26 | spec:
27 | ports:
28 | - name: http
29 | nodePort: 30453
30 | port: 2379
31 | targetPort: 2379
32 | protocol: TCP
33 | type: NodePort
34 | selector:
35 | component: etcd
36 | ...
37 |
--------------------------------------------------------------------------------
/deploy/k8s/etcd-ui-configmap.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: v1
3 | kind: ConfigMap
4 | metadata:
5 | name: e3w-configmap
6 | namespace: etcd
7 | labels:
8 | config: e3w.ini
9 | data:
10 | e3w-config.default.ini: |
11 | [app]
12 | port=8080
13 | auth=false
14 | [etcd]
15 | root_key=root
16 | dir_value=
17 | addr=etcd-0.etcd.etcd.svc.cluster.local:2379,etcd-1.etcd.etcd.svc.cluster.local:2379,etcd-2.etcd.etcd.svc.cluster.local:2379
18 | username=
19 | password=
20 | cert_file=
21 | key_file=
22 | ca_file=
--------------------------------------------------------------------------------
/deploy/k8s/etcd-ui-deployment-service.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: apps/v1
3 | kind: Deployment
4 | metadata:
5 | name: e3w-deployment
6 | namespace: etcd
7 | labels:
8 | app: e3w
9 | spec:
10 | replicas: 1
11 | selector:
12 | matchLabels:
13 | app: etcd-client-e3w
14 | template:
15 | metadata:
16 | labels:
17 | app: etcd-client-e3w
18 | spec:
19 | containers:
20 | - name: e3w-app-container
21 | image: soyking/e3w:latest
22 | ports:
23 | - name: e3w-server-port
24 | containerPort: 8080
25 | volumeMounts:
26 | - name: e3w-configmap-volume
27 | mountPath: /app/conf/config.default.ini
28 | subPath: config.default.ini
29 | volumes:
30 | - name: e3w-configmap-volume
31 | configMap:
32 | name: e3w-configmap
33 | items:
34 | - key: e3w-config.default.ini
35 | path: config.default.ini
36 | ---
37 | kind: Service
38 | apiVersion: v1
39 | metadata:
40 | name: e3w-service
41 | namespace: etcd
42 | spec:
43 | type: NodePort
44 | selector:
45 | app: etcd-client-e3w
46 | ports:
47 | - protocol: TCP
48 | targetPort: e3w-server-port
49 | nodePort: 30081
50 | port: 80
--------------------------------------------------------------------------------
/deploy/k8s/etcd-ui.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: apps/v1
3 | kind: Deployment
4 | metadata:
5 | name: e3w-deployment
6 | namespace: etcd
7 | labels:
8 | app: e3w
9 | spec:
10 | replicas: 1
11 | selector:
12 | matchLabels:
13 | app: etcd-client-e3w
14 | template:
15 | metadata:
16 | labels:
17 | app: etcd-client-e3w
18 | spec:
19 | containers:
20 | - name: e3w-app-container
21 | image: soyking/e3w:latest
22 | ports:
23 | - name: e3w-server-port
24 | containerPort: 8080
25 | ---
26 | kind: Service
27 | apiVersion: v1
28 | metadata:
29 | name: e3w-service
30 | namespace: etcd
31 | spec:
32 | type: NodePort
33 | selector:
34 | app: etcd-client-e3w
35 | ports:
36 | - protocol: TCP
37 | targetPort: e3w-server-port
38 | nodePort: 30081
39 | port: 80
--------------------------------------------------------------------------------
/deploy/k8s/etcd_config.bash:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 |
4 | MINIKUBE_IP=$(minikube ip)
5 | MINIKUBE_PORT=$(kubectl get services -o jsonpath="{.spec.ports[].nodePort}" etcd-client -n etcd)
6 |
7 | cat - |\
8 | sed 's/\$MINIKUBE_IP'"/$MINIKUBE_IP/g" |\
9 | sed 's/\$MINIKUBE_PORT'"/$MINIKUBE_PORT/g"
--------------------------------------------------------------------------------
/deploy/k8s/go-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: go-app-deployment
5 | labels:
6 | app: go-app
7 | spec:
8 | replicas: 1
9 | selector:
10 | matchLabels:
11 | app: go-app
12 | strategy:
13 | type: RollingUpdate
14 | rollingUpdate:
15 | maxSurge: 1
16 | maxUnavailable: 1
17 | template: # Pod 模板的定义
18 | metadata:
19 | labels:
20 | app: go-app
21 | spec: # Pod里容器相关的定义
22 | containers:
23 | - name: go-app
24 | image: 456002253389.dkr.ecr.us-east-1.amazonaws.com/microservices:latest
25 | resources:
26 | limits:
27 | memory: "128Mi"
28 | cpu: "100m" # 0.1核,1000m = 1核心
29 | ports:
30 | - containerPort: 8080
31 | # - containerPort: 9090
32 | # readinessProbe: # 就绪探针
33 | # exec:
34 | # command: [ "/bin/grpc_health_probe", "-addr=:9090" ]
35 | # initialDelaySeconds: 5
36 | # livenessProbe: # 存活探针
37 | # exec:
38 | # command: [ "/bin/grpc_health_probe", "-addr=:9090" ]
39 | # initialDelaySeconds: 10
40 |
--------------------------------------------------------------------------------
/deploy/k8s/go-ingress.yaml:
--------------------------------------------------------------------------------
1 | # Ingress Class
2 | apiVersion: networking.k8s.io/v1
3 | kind: IngressClass
4 | metadata:
5 | name: nginx
6 | spec:
7 | controller: k8s.io/ingress-nginx
8 | ---
9 |
10 | apiVersion: networking.k8s.io/v1
11 | kind: Ingress
12 | metadata:
13 | name: go-app-ingress
14 | annotations:
15 | nginx.ingress.kubernetes.io/use-regex: "true"
16 | spec:
17 | ingressClassName: nginx
18 | rules:
19 | - host: api.go-eagle.org
20 | http:
21 | paths:
22 | - pathType: Prefix
23 | path: "/"
24 | backend:
25 | service:
26 | name: go-app-svc
27 | port:
28 | number: 80
29 | # tls:
30 | # - hosts:
31 | # - api.go-app.org
32 | # secretName: go-app-cert
--------------------------------------------------------------------------------
/deploy/k8s/go-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: go-app-svc
5 | labels:
6 | app: go-app
7 | spec:
8 | selector:
9 | app: go-app
10 | ports:
11 | - protocol: TCP
12 | port: 80
13 | targetPort: 8080
14 | type: LoadBalancer
--------------------------------------------------------------------------------
/deploy/prometheus/README.md:
--------------------------------------------------------------------------------
1 |
2 | 通过启动 prometheus 来采集服务的各种运行指标,方便监控。
3 |
4 | 启动一个 prometheus
5 |
6 | ```bash
7 | prometheus --config.file=prometheus.yml
8 | ```
9 |
10 | 可以通过 http://localhost:8090/ping 来检测服务的运行情况
11 |
12 |
13 | ## Reference
14 |
15 | - https://github.com/yolossn/Prometheus-Basics
--------------------------------------------------------------------------------
/deploy/prometheus/prometheus.yml:
--------------------------------------------------------------------------------
1 | global:
2 | scrape_interval: 15s # 抓取数据的间隔,默认为1分钟
3 | evaluation_interval: 15s # 评估数据的间隔
4 | external_labels:
5 | monitor: 'my-monitor'
6 |
7 | # Alertmanager configuration
8 | #alerting:
9 | # alertmanagers:
10 | # - static_configs:
11 | # - targets:
12 | # - localhost:9093
13 |
14 | # Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
15 | #rule_files:
16 | # - ./rules/rules.yml
17 |
18 | scrape_configs:
19 | # prometheus 自身数据的抓取配置
20 | - job_name: prometheus
21 | static_configs:
22 | - targets: ["localhost:9090"]
23 | - job_name: node_exporter
24 | scrape_interval: 10s
25 | static_configs:
26 | - targets: ["localhost:9100","node_exporter:9100"]
27 |
28 | # Go 程序数据的抓取配置
29 | - job_name: go_app_server
30 | scrape_interval: 10s # 抓取间隔
31 | metrics_path: /metrics # 抓取数据的URL路径
32 | # scheme defaults to 'http'
33 | static_configs:
34 | - targets: ["localhost:8080","app:8080"]
35 | labels:
36 | instance: go-eagle
37 |
--------------------------------------------------------------------------------
/docs/images/clean-arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/go-eagle/eagle/1b5c80f19311299eb51c4f8c4f1dba7adf0e2210/docs/images/clean-arch.png
--------------------------------------------------------------------------------
/docs/images/eagle-layout-arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/go-eagle/eagle/1b5c80f19311299eb51c4f8c4f1dba7adf0e2210/docs/images/eagle-layout-arch.png
--------------------------------------------------------------------------------
/examples/config/config.yaml:
--------------------------------------------------------------------------------
1 | name: eagle
2 | version: 1.0.0
--------------------------------------------------------------------------------
/examples/config/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 |
7 | "github.com/go-eagle/eagle/pkg/config"
8 | )
9 |
10 | func main() {
11 | type Service struct {
12 | Name string
13 | Version string
14 | }
15 |
16 | c := config.New(".")
17 |
18 | var svc Service
19 | err := c.Load("config", &svc)
20 | if err != nil {
21 | log.Fatalf("load config err: %+v", err)
22 | }
23 | fmt.Println("service name: ", svc.Name)
24 |
25 | conf, err := c.LoadWithType("server", "yaml")
26 | if err != nil {
27 | log.Fatalf("load server err: %+v", err)
28 | }
29 | fmt.Println("http addr: ", conf.GetString("http.addr"))
30 | fmt.Println("http port: ", conf.GetInt("http.port"))
31 | }
32 |
--------------------------------------------------------------------------------
/examples/config/server.yaml:
--------------------------------------------------------------------------------
1 | HTTP:
2 | addr: 127.0.0.1
3 | port: 8000
--------------------------------------------------------------------------------
/examples/crontab/app.yaml:
--------------------------------------------------------------------------------
1 | Name: eagle
2 | Version: 1.0.0
3 | PprofPort: :5555
4 | Mode: debug # debug, release, test
5 | JwtSecret: JWT_SECRET
6 | JwtTimeout: 86400
7 | CookieName: jwt-token
8 | SSL: true
9 | CtxDefaultTimeout: 12
10 | CSRF: true
11 | Debug: false
12 | EnableTrace: false
13 | EnablePprof: true
14 |
15 | HTTP:
16 | Addr: :8080
17 | ReadTimeout: 3s
18 | WriteTimeout: 3s
19 | GRPC:
20 | Addr: :9090
21 | ReadTimeout: 5s
22 | WriteTimeout: 5s
--------------------------------------------------------------------------------
/examples/crontab/crontab.yaml:
--------------------------------------------------------------------------------
1 | Timezone: "Asia/Shanghai"
2 | Tasks:
3 | - Name: send_email
4 | Schedule: "* * * * *"
5 | - Name: greet
6 | Schedule: "@every 1s"
7 |
--------------------------------------------------------------------------------
/examples/crontab/jobs.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/go-eagle/eagle/pkg/log"
5 | )
6 |
7 | type GreetingJob struct {
8 | Name string
9 | }
10 |
11 | func (g GreetingJob) Run() {
12 | log.Info("Hello ", g.Name)
13 | }
14 |
15 | type SendEmail struct {
16 | Name string
17 | }
18 |
19 | func (g SendEmail) Run() {
20 | log.Info("Send mail to... ", g.Name)
21 | }
22 |
--------------------------------------------------------------------------------
/examples/crontab/logger.yaml:
--------------------------------------------------------------------------------
1 | Development: false
2 | DisableCaller: false
3 | DisableStacktrace: false
4 | Encoding: console # json or console
5 | Level: info # 日志级别,debug, info, warn, error
6 | Name: eagle
7 | Writers: console # 有2个可选项:file,console 选择file会将日志记录到logger_file指定的日志文件中,选择console会将日志输出到标准输出,当然也可以两者同时选择
8 | LoggerDir: /tmp/logs
9 | LogRollingPolicy: daily
10 | LogRotateDate: 1
11 | LogRotateSize: 1
12 | LogBackupCount: 7
13 |
--------------------------------------------------------------------------------
/examples/crontab/server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | eagle "github.com/go-eagle/eagle/pkg/app"
5 | "github.com/go-eagle/eagle/pkg/config"
6 | logger "github.com/go-eagle/eagle/pkg/log"
7 | "github.com/go-eagle/eagle/pkg/transport/crontab"
8 | "github.com/robfig/cron/v3"
9 | )
10 |
11 | // cd examples/crontab/
12 | // go run server.go jobs.go
13 | func main() {
14 | // init config
15 | c := config.New(".")
16 |
17 | var cfg eagle.Config
18 | if err := c.Load("app", &cfg); err != nil {
19 | panic(err)
20 | }
21 | // set global
22 | eagle.Conf = &cfg
23 |
24 | // init jobs
25 | jobs := map[string]cron.Job{
26 | "greet": GreetingJob{"golang"},
27 | "send_email": SendEmail{" user1"},
28 | }
29 |
30 | log := logger.Init(logger.WithFilename("crontab"))
31 | srv := crontab.NewServer(jobs, crontab.Logger{Log: log})
32 |
33 | // start app
34 | app := eagle.New(
35 | eagle.WithName(cfg.Name),
36 | eagle.WithVersion(cfg.Version),
37 | eagle.WithLogger(log),
38 | eagle.WithServer(
39 | srv,
40 | ),
41 | )
42 |
43 | if err := app.Run(); err != nil {
44 | panic(err)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/examples/helloworld/client/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "flag"
6 | "fmt"
7 | "log"
8 | "time"
9 |
10 | "google.golang.org/grpc"
11 |
12 | pb "github.com/go-eagle/eagle/examples/helloworld/helloworld"
13 | )
14 |
15 | const (
16 | defaultName = "eagle"
17 | )
18 |
19 | var (
20 | addr = flag.String("addr", "localhost:9090", "the address to connect to")
21 | name = flag.String("name", defaultName, "Name to greet")
22 | )
23 |
24 | func main() {
25 | conn, err := grpc.Dial(*addr, grpc.WithInsecure(), grpc.WithBlock())
26 | if err != nil {
27 | fmt.Printf("grpc dial err: %v", err)
28 | panic("grpc dial err")
29 | }
30 | defer func() {
31 | _ = conn.Close()
32 | }()
33 |
34 | cli := pb.NewGreeterClient(conn)
35 | ctx, cancel := context.WithTimeout(context.Background(), time.Second)
36 | defer cancel()
37 |
38 | req := &pb.HelloRequest{
39 | Name: *name,
40 | }
41 | reply, err := cli.SayHello(ctx, req)
42 | if err != nil {
43 | log.Fatalf("could not greet: %v", err)
44 | }
45 | fmt.Printf("Greeting : %s", reply.GetMessage())
46 | }
47 |
--------------------------------------------------------------------------------
/examples/helloworld/helloworld/greeter.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package helloworld;
4 |
5 | option go_package="github.com/go-eagle/eagle/examples/helloworld/helloworld";
6 |
7 | // The greeting service definition.
8 | service Greeter {
9 | // Sends a greeting
10 | rpc SayHello (HelloRequest) returns (HelloReply) {}
11 | }
12 |
13 | // The request message containing the user's name.
14 | message HelloRequest {
15 | string name = 1;
16 | }
17 |
18 | // The response message containing the greetings
19 | message HelloReply {
20 | string message = 1;
21 | }
--------------------------------------------------------------------------------
/examples/helloworld/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | //"google.golang.org/grpc"
8 |
9 | "github.com/go-eagle/eagle/examples/helloworld/helloworld"
10 | pb "github.com/go-eagle/eagle/examples/helloworld/helloworld"
11 | grpcSrv "github.com/go-eagle/eagle/internal/server"
12 | "github.com/go-eagle/eagle/pkg/app"
13 | eagle "github.com/go-eagle/eagle/pkg/app"
14 | logger "github.com/go-eagle/eagle/pkg/log"
15 | )
16 |
17 | // server is used to implement helloworld.GreeterServer.
18 | type server struct {
19 | pb.UnimplementedGreeterServer
20 | }
21 |
22 | // SayHello implements helloworld.GreeterServer
23 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
24 | if in.Name == "" {
25 | return nil, fmt.Errorf("invalid argument %s", in.Name)
26 | }
27 | return &pb.HelloReply{Message: fmt.Sprintf("Hello %+v", in.Name)}, nil
28 | }
29 |
30 | func main() {
31 | cfg := &app.ServerConfig{
32 | Network: "tcp",
33 | Addr: ":9090",
34 | ReadTimeout: 200,
35 | WriteTimeout: 200,
36 | }
37 |
38 | grpcServer := grpcSrv.NewGRPCServer(cfg)
39 | srv := &server{}
40 | helloworld.RegisterGreeterServer(grpcServer, srv)
41 |
42 | // start app
43 | app := eagle.New(
44 | eagle.WithName("eagle"),
45 | eagle.WithVersion("v1.0.0"),
46 | eagle.WithLogger(logger.GetLogger()),
47 | eagle.WithServer(
48 | grpcServer,
49 | ),
50 | )
51 |
52 | if err := app.Run(); err != nil {
53 | panic(err)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/examples/log/logger.yaml:
--------------------------------------------------------------------------------
1 | Development: false
2 | DisableCaller: false
3 | DisableStacktrace: false
4 | Encoding: json # json or console
5 | Level: info # 日志级别,INFO, WARN, ERROR
6 | ServiceName: demo-service
7 | Filename: demo-file
8 | Writers: file # 有2个可选项:file,console 选择file会将日志记录到logger_file指定的日志文件中,选择console会将日志输出到标准输出,当然也可以两者同时选择
9 | LoggerDir: /tmp/logs
10 | LogRollingPolicy: daily
11 | LogRotateDate: 1
12 | LogRotateSize: 1
13 | LogBackupCount: 7
14 |
--------------------------------------------------------------------------------
/examples/log/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/go-eagle/eagle/pkg/config"
5 | "github.com/go-eagle/eagle/pkg/log"
6 | )
7 |
8 | func main() {
9 | // load config
10 | _ = config.New(".")
11 |
12 | // print log using default filename
13 | log.Init()
14 | log.Info("test log")
15 |
16 | // print log using custom filename
17 | log.Init(log.WithFilename("custom-filename"))
18 | log.Info("test log with filename")
19 |
20 | // print log using custom dir and filename
21 | log.Init(log.WithFilename("user/custom"))
22 | log.Info("test log with dir and filename")
23 | }
24 |
--------------------------------------------------------------------------------
/examples/queue/kafka/config/app.yaml:
--------------------------------------------------------------------------------
1 | Name: eagle
2 | Version: 1.0.0
3 | PprofPort: :5555
4 | Mode: debug # debug, release, test
5 | JwtSecret: JWT_SECRET
6 | JwtTimeout: 86400
7 | CookieName: jwt-token
8 | SSL: true
9 | CtxDefaultTimeout: 12
10 | CSRF: true
11 | Debug: false
12 | EnableTrace: false
13 | EnablePprof: true
14 |
15 | HTTP:
16 | Addr: :8080
17 | ReadTimeout: 3s
18 | WriteTimeout: 3s
19 | GRPC:
20 | Addr: :9090
21 | ReadTimeout: 5s
22 | WriteTimeout: 5s
--------------------------------------------------------------------------------
/examples/queue/kafka/config/kafka.yaml:
--------------------------------------------------------------------------------
1 | default: # 实例名称
2 | Version: "2.8.1"
3 | RequiredAcks: 1
4 | Topic: "test-topic"
5 | ConsumeTopic:
6 | - "test-topic1"
7 | - "test-topic2"
8 | Brokers:
9 | - "localhost:9092"
10 | GroupID: "test-group"
11 | Partitioner: "hash"
12 |
13 | order: # 另一个实例配置
14 | Version: "2.8.1"
15 | RequiredAcks: 1
16 | Topic: "order-topic"
17 | ConsumeTopic:
18 | - "order-topic"
19 | Brokers:
20 | - "localhost:9092"
21 | GroupID: "order-group"
22 | Partitioner: "random"
23 |
--------------------------------------------------------------------------------
/examples/queue/kafka/config/logger.yaml:
--------------------------------------------------------------------------------
1 | Development: false
2 | DisableCaller: false
3 | DisableStacktrace: false
4 | Encoding: json # json or console
5 | Level: info # 日志级别,INFO, WARN, ERROR
6 | Name: eagle
7 | Writers: console # 有2个可选项:file,console 选择file会将日志记录到logger_file指定的日志文件中,选择console会将日志输出到标准输出,当然也可以两者同时选择
8 | LoggerFile: /tmp/log/eagle.log
9 | LoggerWarnFile: /tmp/log/eagle.wf.log
10 | LoggerErrorFile: /tmp/log/eagle.err.log
11 | LogRollingPolicy: daily
12 | LogRotateDate: 1
13 | LogRotateSize: 1
14 | LogBackupCount: 7
--------------------------------------------------------------------------------
/examples/queue/kafka/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "log"
6 |
7 | "github.com/go-eagle/eagle/pkg/queue/kafka"
8 | )
9 |
10 | func main() {
11 | // 1. 初始化配置
12 | kafka.Load()
13 | defer kafka.Close()
14 |
15 | // 2. 获取配置信息(可选)
16 | configs := kafka.GetConfig()
17 | if len(configs) == 0 {
18 | log.Fatal("No kafka config found")
19 | }
20 |
21 | // 3. 使用配置进行消息发布
22 | ctx := context.Background()
23 | err := kafka.Publish(ctx, "default", "test-topic", "hello world")
24 | if err != nil {
25 | log.Printf("Failed to publish message: %v", err)
26 | }
27 |
28 | // 4. 使用配置进行消息消费
29 | handler := func(data []byte) error {
30 | log.Printf("Received message: %s", string(data))
31 | return nil
32 | }
33 |
34 | // 从默认实例消费
35 | go func() {
36 | err := kafka.ConsumePartition(ctx, "default", "test-topic", handler)
37 | if err != nil {
38 | log.Printf("Failed to consume message: %v", err)
39 | }
40 | }()
41 |
42 | // 从order实例消费
43 | go func() {
44 | err := kafka.ConsumePartition(ctx, "order", "order-topic", handler)
45 | if err != nil {
46 | log.Printf("Failed to consume message: %v", err)
47 | }
48 | }()
49 |
50 | select {}
51 | }
52 |
--------------------------------------------------------------------------------
/examples/queue/nats/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/base64"
5 | "log"
6 | "strings"
7 | "sync"
8 | "time"
9 |
10 | "github.com/go-eagle/eagle/pkg/queue/nats"
11 | )
12 |
13 | func main() {
14 | var (
15 | addr = "nats://localhost:4222"
16 | topic = "hello"
17 | )
18 | producer := nats.NewProducer(addr)
19 | consumer := nats.NewConsumer(addr)
20 |
21 | published := make(chan struct{})
22 | received := make(chan struct{})
23 | wg := sync.WaitGroup{}
24 | wg.Add(2)
25 | go func() {
26 | for {
27 | select {
28 | case <-published:
29 | time.Sleep(3 * time.Second)
30 | if err := producer.Publish(topic, []byte("hello nats")); err != nil {
31 | log.Fatal(err)
32 | }
33 | log.Println("producer handler publish msg: ", "hello nats")
34 |
35 | case <-received:
36 | wg.Done()
37 | break
38 | }
39 | }
40 | }()
41 | go func() {
42 | for {
43 | // nolint: gosimple
44 | select {
45 | default:
46 | handler := func(message []byte) error {
47 | decodeMessage, _ := base64.StdEncoding.DecodeString(strings.Trim(string(message), "\""))
48 | log.Println("consumer handler receive msg: ", string(decodeMessage))
49 | received <- struct{}{}
50 | wg.Done()
51 | return nil
52 | }
53 | if err := consumer.Consume(topic, handler); err != nil {
54 | log.Fatal(err)
55 | }
56 | time.Sleep(5 * time.Second)
57 | }
58 | }
59 | }()
60 |
61 | published <- struct{}{}
62 | wg.Wait()
63 | }
64 |
--------------------------------------------------------------------------------
/examples/queue/rabbitmq/consumer/config/app.yaml:
--------------------------------------------------------------------------------
1 | Name: eagle
2 | Version: 1.0.0
3 | PprofPort: :5555
4 | Mode: debug # debug, release, test
5 | JwtSecret: JWT_SECRET
6 | JwtTimeout: 86400
7 | CookieName: jwt-token
8 | SSL: true
9 | CtxDefaultTimeout: 12
10 | CSRF: true
11 | Debug: false
12 | EnableTrace: false
13 | EnablePprof: true
14 |
15 | HTTP:
16 | Addr: :8080
17 | ReadTimeout: 3s
18 | WriteTimeout: 3s
19 | GRPC:
20 | Addr: :9090
21 | ReadTimeout: 5s
22 | WriteTimeout: 5s
--------------------------------------------------------------------------------
/examples/queue/rabbitmq/consumer/config/logger.yaml:
--------------------------------------------------------------------------------
1 | Development: false
2 | DisableCaller: false
3 | DisableStacktrace: false
4 | Encoding: console # json or console
5 | Level: info # 日志级别,INFO, WARN, ERROR
6 | Name: eagle
7 | Writers: console # 有2个可选项:file,console 选择file会将日志记录到logger_file指定的日志文件中,选择console会将日志输出到标准输出,当然也可以两者同时选择
8 | LoggerFile: /tmp/log/eagle.log
9 | LoggerWarnFile: /tmp/log/eagle.wf.log
10 | LoggerErrorFile: /tmp/log/eagle.err.log
11 | LogRollingPolicy: daily
12 | LogRotateDate: 1
13 | LogRotateSize: 1
14 | LogBackupCount: 7
--------------------------------------------------------------------------------
/examples/queue/rabbitmq/consumer/config/rabbitmq.yaml:
--------------------------------------------------------------------------------
1 | test-demo:
2 | URI: "amqp://guest:guest@localhost:5672/"
3 | AutoDeclare: true
4 | Timeout: 5s
5 | Exchange:
6 | Name: local-test-exchange
7 | Kind: direct
8 | Durable: true
9 | AutoDelete: false
10 | Internal: false
11 | NoWait: false
12 | Args: {}
13 | Queue:
14 | name: local-test-queue
15 | durable: true
16 | AutoDelete: false
17 | Exclusive: false
18 | NoWait: false
19 | Args: {}
20 | Bind:
21 | RoutingKey: local-test-routing-key
22 | NoWait: false
23 | Args: {}
24 |
25 | test-multi:
26 | URI: "amqp://guest:guest@localhost:5672/"
27 | AutoDeclare: true
28 | Timeout: 5s
29 | Exchange:
30 | Name: local-test2-exchange
31 | Kind: direct
32 | Durable: true
33 | AutoDelete: false
34 | Internal: false
35 | NoWait: false
36 | Args: {}
37 | Queue:
38 | name: local-test2-queue
39 | durable: true
40 | AutoDelete: false
41 | Exclusive: false
42 | NoWait: false
43 | Args: {}
44 | Bind:
45 | RoutingKey: local-test2-routing-key
46 | NoWait: false
47 | Args: {}
--------------------------------------------------------------------------------
/examples/queue/rabbitmq/producer/config/app.yaml:
--------------------------------------------------------------------------------
1 | Name: eagle
2 | Version: 1.0.0
3 | PprofPort: :5555
4 | Mode: debug # debug, release, test
5 | JwtSecret: JWT_SECRET
6 | JwtTimeout: 86400
7 | CookieName: jwt-token
8 | SSL: true
9 | CtxDefaultTimeout: 12
10 | CSRF: true
11 | Debug: false
12 | EnableTrace: false
13 | EnablePprof: true
14 |
15 | HTTP:
16 | Addr: :8080
17 | ReadTimeout: 3s
18 | WriteTimeout: 3s
19 | GRPC:
20 | Addr: :9090
21 | ReadTimeout: 5s
22 | WriteTimeout: 5s
--------------------------------------------------------------------------------
/examples/queue/rabbitmq/producer/config/logger.yaml:
--------------------------------------------------------------------------------
1 | Development: false
2 | DisableCaller: false
3 | DisableStacktrace: false
4 | Encoding: json # json or console
5 | Level: info # 日志级别,INFO, WARN, ERROR
6 | Name: eagle
7 | Writers: console # 有2个可选项:file,console 选择file会将日志记录到logger_file指定的日志文件中,选择console会将日志输出到标准输出,当然也可以两者同时选择
8 | LoggerFile: /tmp/log/eagle.log
9 | LoggerWarnFile: /tmp/log/eagle.wf.log
10 | LoggerErrorFile: /tmp/log/eagle.err.log
11 | LogRollingPolicy: daily
12 | LogRotateDate: 1
13 | LogRotateSize: 1
14 | LogBackupCount: 7
--------------------------------------------------------------------------------
/examples/queue/rabbitmq/producer/delay_publish.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "log"
7 | "time"
8 |
9 | eagle "github.com/go-eagle/eagle/pkg/app"
10 | "github.com/go-eagle/eagle/pkg/config"
11 | logger "github.com/go-eagle/eagle/pkg/log"
12 | "github.com/go-eagle/eagle/pkg/queue/rabbitmq"
13 | "github.com/go-eagle/eagle/pkg/queue/rabbitmq/options"
14 | )
15 |
16 | // 启动 rabbitmq
17 | // docker run -it --name rabbitmq -p 5672:5672 -p 15672:15672 -v $PWD/plugins:/plugins rabbitmq:3.10-management
18 | // 访问ui: http://127.0.0.1:15672/
19 | // cd examples/queue/rabbitmq/producer
20 | // go run delay_publish.go
21 | func main() {
22 | c := config.New(*cfgDir, config.WithEnv(*env))
23 | var cfg eagle.Config
24 | if err := c.Load("app", &cfg); err != nil {
25 | panic(err)
26 | }
27 | // set global
28 | eagle.Conf = &cfg
29 |
30 | logger.Init()
31 |
32 | rabbitmq.Load()
33 | defer rabbitmq.Close()
34 |
35 | opts := []options.PublishOption{
36 | options.WithPublishOptionContentType("application/json"),
37 | }
38 |
39 | var message string
40 | for i := 0; i < 100000; i++ {
41 | message = "Hello World RabbitMQ!" + time.Now().String()
42 | msg := map[string]interface{}{
43 | "message": message,
44 | }
45 | data, _ := json.Marshal(msg)
46 | if err := rabbitmq.PublishWithDelay(context.Background(), "test-demo", data, 10, opts...); err != nil {
47 | log.Fatalf("failed publish message: %s", err.Error())
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/examples/queue/redis/app.yaml:
--------------------------------------------------------------------------------
1 | Name: eagle
2 | Version: 1.0.0
3 | PprofPort: :5555
4 | Mode: debug # debug, release, test
5 | JwtSecret: JWT_SECRET
6 | JwtTimeout: 86400
7 | CookieName: jwt-token
8 | SSL: true
9 | CtxDefaultTimeout: 12
10 | CSRF: true
11 | Debug: false
12 | EnableTrace: false
13 | EnablePprof: true
14 |
15 | HTTP:
16 | Addr: :8080
17 | ReadTimeout: 3s
18 | WriteTimeout: 3s
19 | GRPC:
20 | Addr: :9090
21 | ReadTimeout: 5s
22 | WriteTimeout: 5s
--------------------------------------------------------------------------------
/examples/queue/redis/client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "sync"
5 | "time"
6 |
7 | "github.com/go-eagle/eagle/pkg/config"
8 | "github.com/hibiken/asynq"
9 | )
10 |
11 | var (
12 | client *asynq.Client
13 | once sync.Once
14 | )
15 |
16 | type Config struct {
17 | Queue struct {
18 | Addr string
19 | Password string
20 | DB int
21 | MinIdleConn int
22 | DialTimeout time.Duration
23 | ReadTimeout time.Duration
24 | WriteTimeout time.Duration
25 | PoolSize int
26 | PoolTimeout time.Duration
27 | Concurrency int //并发数
28 | } `json:"redis"`
29 | }
30 |
31 | func GetClient() *asynq.Client {
32 | once.Do(func() {
33 | //c := config.New("config", config.WithEnv("local"))
34 | c := config.New(".")
35 | var cfg Config
36 | if err := c.Load("redis", &cfg); err != nil {
37 | panic(err)
38 | }
39 | client = asynq.NewClient(asynq.RedisClientOpt{
40 | Addr: cfg.Queue.Addr,
41 | Password: cfg.Queue.Password,
42 | DB: cfg.Queue.DB,
43 | DialTimeout: cfg.Queue.DialTimeout,
44 | ReadTimeout: cfg.Queue.ReadTimeout,
45 | WriteTimeout: cfg.Queue.WriteTimeout,
46 | PoolSize: cfg.Queue.PoolSize,
47 | })
48 | })
49 | return client
50 | }
51 |
--------------------------------------------------------------------------------
/examples/queue/redis/logger.yaml:
--------------------------------------------------------------------------------
1 | Development: false
2 | DisableCaller: false
3 | DisableStacktrace: false
4 | Encoding: console # json or console
5 | Level: info # 日志级别,INFO, WARN, ERROR
6 | Name: eagle
7 | Writers: console # 有2个可选项:file,console 选择file会将日志记录到logger_file指定的日志文件中,选择console会将日志输出到标准输出,当然也可以两者同时选择
8 | LoggerFile: /tmp/log/eagle.log
9 | LoggerWarnFile: /tmp/log/eagle.wf.log
10 | LoggerErrorFile: /tmp/log/eagle.err.log
11 | LogRollingPolicy: daily
12 | LogRotateDate: 1
13 | LogRotateSize: 1
14 | LogBackupCount: 7
--------------------------------------------------------------------------------
/examples/queue/redis/producer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "time"
4 |
5 | // cd examples/queue/redis/consumer/
6 | // go run producer.go handler.go client.go
7 | func main() {
8 | for i := 0; i < 10; i++ {
9 | err := NewEmailWelcomeTask(EmailWelcomePayload{
10 | UserID: time.Now().Unix(),
11 | })
12 | if err != nil {
13 | panic(err)
14 | }
15 | time.Sleep(time.Second)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/queue/redis/redis.yaml:
--------------------------------------------------------------------------------
1 | Queue:
2 | Addr: 127.0.0.1:6379
3 | Password: ""
4 | DB: 0
5 | MinIdleConn: 200
6 | DialTimeout: 60s
7 | ReadTimeout: 500ms
8 | WriteTimeout: 500ms
9 | PoolSize: 100
10 | PoolTimeout: 240s
11 | EnableTrace: true
12 |
--------------------------------------------------------------------------------
/examples/queue/redis/server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | eagle "github.com/go-eagle/eagle/pkg/app"
5 | "github.com/go-eagle/eagle/pkg/config"
6 | logger "github.com/go-eagle/eagle/pkg/log"
7 | redisMQ "github.com/go-eagle/eagle/pkg/transport/consumer/redis"
8 | "github.com/hibiken/asynq"
9 | "github.com/spf13/pflag"
10 | )
11 |
12 | // redis queue consumer
13 | // cd examples/queue/redis/consumer/
14 | // go run server.go handler.go client.go
15 | func main() {
16 | pflag.Parse()
17 |
18 | // init config
19 | c := config.New(".")
20 | var cfg eagle.Config
21 | if err := c.Load("app", &cfg); err != nil {
22 | panic(err)
23 | }
24 | // set global
25 | eagle.Conf = &cfg
26 |
27 | logger.Init()
28 |
29 | srv := redisMQ.NewServer(
30 | asynq.RedisClientOpt{Addr: "localhost:6379"},
31 | asynq.Config{
32 | // Specify how many concurrent workers to use
33 | Concurrency: 10,
34 | // Optionally specify multiple queues with different priority.
35 | Queues: map[string]int{
36 | redisMQ.QueueCritical: 6,
37 | redisMQ.QueueDefault: 3,
38 | redisMQ.QueueLow: 1,
39 | },
40 | // See the godoc for other configuration options
41 | },
42 | )
43 |
44 | // register handler
45 | srv.RegisterHandler(TypeEmailWelcome, HandleEmailWelcomeTask)
46 | // here register other handlers...
47 |
48 | // start app
49 | app := eagle.New(
50 | eagle.WithName(cfg.Name),
51 | eagle.WithVersion(cfg.Version),
52 | eagle.WithLogger(logger.GetLogger()),
53 | eagle.WithServer(
54 | srv,
55 | ),
56 | )
57 |
58 | if err := app.Run(); err != nil {
59 | panic(err)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/internal/cache/cache_client.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/go-eagle/eagle/internal/model"
7 | "github.com/go-eagle/eagle/pkg/cache"
8 | "github.com/go-eagle/eagle/pkg/encoding"
9 | "github.com/go-eagle/eagle/pkg/redis"
10 | )
11 |
12 | func getCacheClient(ctx context.Context) cache.Cache {
13 | jsonEncoding := encoding.JSONEncoding{}
14 | cachePrefix := ""
15 | client := cache.NewRedisCache(redis.RedisClient, cachePrefix, jsonEncoding, func() interface{} {
16 | return &model.UserBaseModel{}
17 | })
18 |
19 | return client
20 | }
21 |
--------------------------------------------------------------------------------
/internal/ecode/README.md:
--------------------------------------------------------------------------------
1 | # 业务错误码定义
2 |
3 | > 公共错误码已经在 `github.com/go-eagle/eagle/pkg/errno` 包中,可以直接使用
4 |
5 | 业务的错误码可以根据模块按文件进行定义
6 |
7 | 使用时公共错误码 以 `errno.开头`,业务错误码以 `ecode.开头`
8 |
9 | ## Demo
10 |
11 | ```go
12 | // 公共错误码
13 | import "github.com/go-eagle/eagle/pkg/errno"
14 | ...
15 | errno.InternalServerError
16 |
17 | // 业务错误码
18 | import "github.com/go-eagle/eagle/internal/ecode"
19 | ...
20 | ecode.ErrUserNotFound
21 | ```
--------------------------------------------------------------------------------
/internal/ecode/user.go:
--------------------------------------------------------------------------------
1 | package ecode
2 |
3 | import "github.com/go-eagle/eagle/pkg/errcode"
4 |
5 | //nolint: golint
6 | var (
7 | // user errors
8 | ErrUserNotFound = errcode.NewError(20101, "The user was not found.")
9 | ErrPasswordIncorrect = errcode.NewError(20102, "账号或密码错误")
10 | ErrAreaCodeEmpty = errcode.NewError(20103, "手机区号不能为空")
11 | ErrPhoneEmpty = errcode.NewError(20104, "手机号不能为空")
12 | ErrGenVCode = errcode.NewError(20105, "生成验证码错误")
13 | ErrSendSMS = errcode.NewError(20106, "发送短信错误")
14 | ErrSendSMSTooMany = errcode.NewError(20107, "已超出当日限制,请明天再试")
15 | ErrVerifyCode = errcode.NewError(20108, "验证码错误")
16 | ErrEmailOrPassword = errcode.NewError(20109, "邮箱或密码错误")
17 | ErrTwicePasswordNotMatch = errcode.NewError(20110, "两次密码输入不一致")
18 | ErrRegisterFailed = errcode.NewError(20111, "注册失败")
19 |
20 | ErrCannotFollowSelf = errcode.NewError(20201, "不能关注自己")
21 | )
22 |
--------------------------------------------------------------------------------
/internal/handler/v1/user/get.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/gin-gonic/gin"
7 | "github.com/spf13/cast"
8 |
9 | "github.com/go-eagle/eagle/internal/ecode"
10 | "github.com/go-eagle/eagle/internal/repository"
11 | "github.com/go-eagle/eagle/internal/service"
12 | "github.com/go-eagle/eagle/pkg/errcode"
13 | "github.com/go-eagle/eagle/pkg/log"
14 | )
15 |
16 | // Get 获取用户信息
17 | // @Summary 通过用户id获取用户信息
18 | // @Description Get an user by user id
19 | // @Tags 用户
20 | // @Accept json
21 | // @Produce json
22 | // @Param id path string true "用户id"
23 | // @Success 200 {object} model.UserInfo "用户信息"
24 | // @Router /users/:id [get]
25 | func Get(c *gin.Context) {
26 | log.Info("Get function called.")
27 |
28 | userID := cast.ToUint64(c.Param("id"))
29 | if userID == 0 {
30 | response.Error(c, errcode.ErrInvalidParam)
31 | return
32 | }
33 |
34 | // Get the user by the `user_id` from the database.
35 | u, err := service.Svc.Users().GetUserByID(c.Request.Context(), userID)
36 | if errors.Is(err, repository.ErrNotFound) {
37 | log.Errorf("get user info err: %+v", err)
38 | response.Error(c, ecode.ErrUserNotFound)
39 | return
40 | }
41 | if err != nil {
42 | response.Error(c, errcode.ErrInternalServer.WithDetails(err.Error()))
43 | return
44 | }
45 |
46 | //time.Sleep(5 * time.Second)
47 |
48 | response.Success(c, u)
49 | }
50 |
--------------------------------------------------------------------------------
/internal/handler/v1/user/register.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 |
6 | "github.com/go-eagle/eagle/internal/ecode"
7 | "github.com/go-eagle/eagle/internal/service"
8 | "github.com/go-eagle/eagle/pkg/errcode"
9 | "github.com/go-eagle/eagle/pkg/log"
10 | )
11 |
12 | // Register 注册
13 | // @Summary 注册
14 | // @Description 用户注册
15 | // @Tags 用户
16 | // @Produce json
17 | // @Param req body RegisterRequest true "请求参数"
18 | // @Success 200 {object} model.UserInfo "用户信息"
19 | // @Router /Register [post]
20 | func Register(c *gin.Context) {
21 | // Binding the data with the u struct.
22 | var req RegisterRequest
23 | if err := c.ShouldBindJSON(&req); err != nil {
24 | log.Warnf("register bind param err: %v", err)
25 | response.Error(c, errcode.ErrInvalidParam)
26 | return
27 | }
28 |
29 | log.Infof("register req: %#v", req)
30 | // check param
31 | if req.Username == "" || req.Email == "" || req.Password == "" {
32 | log.Warnf("params is empty: %v", req)
33 | response.Error(c, errcode.ErrInvalidParam)
34 | return
35 | }
36 |
37 | // 两次密码是否正确
38 | if req.Password != req.ConfirmPassword {
39 | log.Warnf("twice password is not same")
40 | response.Error(c, ecode.ErrTwicePasswordNotMatch)
41 | return
42 | }
43 |
44 | err := service.Svc.Users().Register(c, req.Username, req.Email, req.Password)
45 | if err != nil {
46 | log.Warnf("register err: %v", err)
47 | response.Error(c, ecode.ErrRegisterFailed.WithDetails(err.Error()))
48 | return
49 | }
50 |
51 | response.Success(c, nil)
52 | }
53 |
--------------------------------------------------------------------------------
/internal/handler/v1/user/update.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/go-eagle/eagle/internal/service"
7 |
8 | "github.com/gin-gonic/gin"
9 | "github.com/spf13/cast"
10 |
11 | "github.com/go-eagle/eagle/pkg/errcode"
12 | "github.com/go-eagle/eagle/pkg/log"
13 | )
14 |
15 | // Update 更新用户信息
16 | // @Summary Update a user info by the user identifier
17 | // @Description Update a user by ID
18 | // @Tags 用户
19 | // @Accept json
20 | // @Produce json
21 | // @Param id path uint64 true "The user's database id index num"
22 | // @Param user body model.UserBaseModel true "The user info"
23 | // @Success 200 {object} app.Response "{"code":0,"message":"OK","data":null}"
24 | // @Router /users/{id} [put]
25 | func Update(c *gin.Context) {
26 | // Get the user id from the url parameter.
27 | userID := cast.ToUint64(c.Param("id"))
28 |
29 | // Binding the user data.
30 | var req UpdateRequest
31 | if err := c.Bind(&req); err != nil {
32 | log.Warnf("bind request param err: %+v", err)
33 | response.Error(c, errcode.ErrInvalidParam)
34 | return
35 | }
36 | log.Infof("user update req: %#v", req)
37 |
38 | userMap := make(map[string]interface{})
39 | userMap["avatar"] = req.Avatar
40 | userMap["sex"] = req.Sex
41 | err := service.Svc.Users().UpdateUser(context.TODO(), userID, userMap)
42 | if err != nil {
43 | log.Warnf("[user] update user err, %v", err)
44 | response.Error(c, errcode.ErrInternalServer)
45 | return
46 | }
47 |
48 | response.Success(c, userID)
49 | }
50 |
--------------------------------------------------------------------------------
/internal/handler/v1/user/vcode.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/pkg/errors"
6 |
7 | "github.com/go-eagle/eagle/internal/ecode"
8 | "github.com/go-eagle/eagle/internal/service"
9 | "github.com/go-eagle/eagle/pkg/log"
10 | )
11 |
12 | // VCode 获取验证码
13 | // @Summary 根据手机号获取校验码
14 | // @Description Get an user by username
15 | // @Tags 用户
16 | // @Accept json
17 | // @Produce json
18 | // @Param area_code query string true "区域码,比如86"
19 | // @Param phone query string true "手机号"
20 | // @Success 200 {object} app.Response
21 | // @Router /vcode [get]
22 | func VCode(c *gin.Context) {
23 | // 验证区号和手机号是否为空
24 | if c.Query("area_code") == "" {
25 | log.Warn("vcode area code is empty")
26 | response.Error(c, ecode.ErrAreaCodeEmpty)
27 | return
28 | }
29 |
30 | phone := c.Query("phone")
31 | if phone == "" {
32 | log.Warn("vcode phone is empty")
33 | response.Error(c, ecode.ErrPhoneEmpty)
34 | return
35 | }
36 |
37 | // TODO: 频率控制,以防攻击
38 |
39 | // 生成短信验证码
40 | verifyCode, err := service.Svc.VCode().GenLoginVCode(phone)
41 | if err != nil {
42 | log.Warnf("gen login verify code err, %v", errors.WithStack(err))
43 | response.Error(c, ecode.ErrGenVCode)
44 | return
45 | }
46 |
47 | // 发送短信
48 | err = service.Svc.SMS().SendSMS(phone, verifyCode)
49 | if err != nil {
50 | log.Warnf("send phone sms err, %v", errors.WithStack(err))
51 | response.Error(c, ecode.ErrSendSMS)
52 | return
53 | }
54 |
55 | response.Success(c, nil)
56 | }
57 |
--------------------------------------------------------------------------------
/internal/middleware/README.md:
--------------------------------------------------------------------------------
1 | # 中间件
2 |
3 | 业务自定义的中间可以放本目录
--------------------------------------------------------------------------------
/internal/middleware/translations.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | // nolint
4 | import (
5 | en_translations "github.com/go-playground/validator/v10/translations/en"
6 | zh_translations "github.com/go-playground/validator/v10/translations/zh"
7 |
8 | "github.com/gin-gonic/gin"
9 | "github.com/gin-gonic/gin/binding"
10 | "github.com/go-playground/locales/en"
11 | "github.com/go-playground/locales/zh"
12 | "github.com/go-playground/locales/zh_Hant_TW"
13 | "github.com/go-playground/universal-translator"
14 | "github.com/go-playground/validator/v10"
15 | )
16 |
17 | var uni = ut.New(en.New(), zh.New(), zh_Hant_TW.New())
18 |
19 | // Translations .
20 | func Translations() gin.HandlerFunc {
21 | return func(c *gin.Context) {
22 | locale := c.GetHeader("locale")
23 | trans, _ := uni.GetTranslator(locale)
24 | v, ok := binding.Validator.Engine().(*validator.Validate)
25 | if ok {
26 | switch locale {
27 | case "zh":
28 | _ = zh_translations.RegisterDefaultTranslations(v, trans)
29 | case "en":
30 | _ = en_translations.RegisterDefaultTranslations(v, trans)
31 | default:
32 | _ = zh_translations.RegisterDefaultTranslations(v, trans)
33 | }
34 | c.Set("trans", trans)
35 | }
36 |
37 | c.Next()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/internal/model/README.md:
--------------------------------------------------------------------------------
1 | # Model
2 |
3 | Model层,或者叫 `Entity`,实体层,用于存放我们的实体类,与数据库中的属性值基本保持一致。
4 |
5 | 通过http访问返回的结构体也都放到这里,在输出前进行结构体的转换。一般 `XXXInfo` 的形式。
6 | 比如: 在返回终端用户前对 `userModel` 进行转换,转换为结构体 `UserInfo` 。
7 |
8 | ## 数据库约定
9 |
10 | 这里默认使用 `MySQL` 数据库,尽量使用 `InnoDB` 作为存储引擎。
11 |
12 | ### 相关表采用统一前缀
13 |
14 | 比如和用户相关的,使用 `user_` 作为表前缀:
15 |
16 | ```bash
17 | user_base // 用户基础表
18 | user_follow // 用户关注表
19 | user_fans // 用户粉丝表
20 | user_stat // 用户统计表
21 | ```
22 |
23 | ### 统一字段名
24 |
25 | 一个表中需要包含的三大字段:主键(id),创建时间(created_at),更新时间(updated_at)
26 | 如果需要用户id,一般用 `user_id` 表示即可。
--------------------------------------------------------------------------------
/internal/model/init.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "fmt"
5 |
6 | "gorm.io/gorm"
7 |
8 | "github.com/go-eagle/eagle/pkg/storage/orm"
9 | )
10 |
11 | const (
12 | // DefaultDatabase default database
13 | DefaultDatabase = "default"
14 | // UserDatabase user database
15 | UserDatabase = "user"
16 | )
17 |
18 | // Init 初始化数据库
19 | func Init() {
20 | err := orm.New(
21 | DefaultDatabase,
22 | UserDatabase,
23 | )
24 | if err != nil {
25 | panic(fmt.Sprintf("new orm database err: %v", err))
26 | }
27 | }
28 |
29 | // GetDB 返回默认的数据库
30 | func GetDB() (*gorm.DB, error) {
31 | return orm.GetDB(DefaultDatabase)
32 | }
33 |
34 | // GetUserDB 获取用户数据库实例
35 | func GetUserDB() (*gorm.DB, error) {
36 | return orm.GetDB(UserDatabase)
37 | }
38 |
--------------------------------------------------------------------------------
/internal/model/user_fans.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "time"
4 |
5 | // UserFansModel 粉丝表
6 | type UserFansModel struct {
7 | ID uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"`
8 | FollowerUID uint64 `gorm:"column:follower_uid" json:"follower_uid"`
9 | Status int `gorm:"column:status" json:"status"`
10 | UserID uint64 `gorm:"column:user_id" json:"user_id"`
11 | CreatedAt time.Time `gorm:"column:created_at" json:"-"`
12 | UpdatedAt time.Time `gorm:"column:updated_at" json:"-"`
13 | }
14 |
15 | // TableName sets the insert table name for this struct type
16 | func (u *UserFansModel) TableName() string {
17 | return "user_fans"
18 | }
19 |
--------------------------------------------------------------------------------
/internal/model/user_follow.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "time"
4 |
5 | // UserFollowModel 关注表
6 | type UserFollowModel struct {
7 | ID uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"`
8 | FollowedUID uint64 `gorm:"column:followed_uid" json:"followed_uid"`
9 | Status int `gorm:"column:status" json:"status"`
10 | UserID uint64 `gorm:"column:user_id" json:"user_id"`
11 | CreatedAt time.Time `gorm:"column:created_at" json:"-"`
12 | UpdatedAt time.Time `gorm:"column:updated_at" json:"-"`
13 | }
14 |
15 | // TableName sets the insert table name for this struct type
16 | func (u *UserFollowModel) TableName() string {
17 | return "user_follow"
18 | }
19 |
--------------------------------------------------------------------------------
/internal/model/user_stat.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "time"
4 |
5 | // UserStatModel 用户数据统计表
6 | type UserStatModel struct {
7 | ID uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"`
8 | UserID uint64 `gorm:"column:user_id;not null" json:"user_id" binding:"required"`
9 | FollowCount int `gorm:"column:follow_count" json:"follow_count"`
10 | FollowerCount int `gorm:"column:follower_count" json:"follower_count"`
11 | Status int `gorm:"column:status" json:"status"`
12 | CreatedAt time.Time `gorm:"column:created_at" json:"-"`
13 | UpdatedAt time.Time `gorm:"column:updated_at" json:"-"`
14 | }
15 |
16 | // TableName 表名
17 | func (u *UserStatModel) TableName() string {
18 | return "user_stat"
19 | }
20 |
--------------------------------------------------------------------------------
/internal/pkg/README.md:
--------------------------------------------------------------------------------
1 |
2 | # 业务公共模块
3 |
4 | - 重复检测
5 | - counter
6 | - id生成器
--------------------------------------------------------------------------------
/internal/pkg/idalloc.go:
--------------------------------------------------------------------------------
1 | // Package pkg ID 分配器,主要使用redis进行分配
2 | package pkg
3 |
4 | import "github.com/go-eagle/eagle/pkg/redis"
5 |
6 | // IDAlloc define struct
7 | type IDAlloc struct {
8 | idGenerator *redis.IDAlloc
9 | }
10 |
11 | // NewIDAlloc create a id alloc
12 | func NewIDAlloc() *IDAlloc {
13 | return &IDAlloc{
14 | idGenerator: redis.NewIDAlloc(redis.RedisClient),
15 | }
16 | }
17 |
18 | // GetUserID generate user id from redis
19 | func (i *IDAlloc) GetUserID() (int64, error) {
20 | return i.idGenerator.GetNewID("user_id", 1)
21 | }
22 |
--------------------------------------------------------------------------------
/internal/pkg/repeat.go:
--------------------------------------------------------------------------------
1 | // Package pkg 重复提交模型封装
2 | package pkg
3 |
4 | import (
5 | "fmt"
6 | "time"
7 |
8 | "github.com/go-eagle/eagle/pkg/log"
9 | "github.com/go-eagle/eagle/pkg/redis"
10 | "github.com/go-eagle/eagle/pkg/utils"
11 | )
12 |
13 | // CRepeat define struct
14 | type CRepeat struct {
15 | cRepeatClient redis.CheckRepeat
16 | }
17 |
18 | // NewCRepeat create a check repeat
19 | func NewCRepeat() *CRepeat {
20 | return &CRepeat{
21 | cRepeatClient: redis.NewCheckRepeat(redis.RedisClient),
22 | }
23 | }
24 |
25 | // getKey return a check repeat key
26 | func (c *CRepeat) getKey(userID int64, check string) string {
27 | key, err := utils.Md5(fmt.Sprintf("%d:%s", userID, check))
28 | if err != nil {
29 | log.Warnf("md5 string err: %v", err)
30 | }
31 | return key
32 | }
33 |
34 | // Set record a repeat value
35 | func (c *CRepeat) Set(userID int64, check string, value interface{}, expiration time.Duration) error {
36 | return c.cRepeatClient.Set(c.getKey(userID, check), value, expiration)
37 | }
38 |
39 | // SetNX set
40 | func (c *CRepeat) SetNX(userID int64, check string, value interface{}, expiration time.Duration) (bool, error) {
41 | return c.cRepeatClient.SetNX(c.getKey(userID, check), value, expiration)
42 | }
43 |
44 | // Get get value
45 | func (c *CRepeat) Get(userID int64, check string) (interface{}, error) {
46 | return c.cRepeatClient.Get(c.getKey(userID, check))
47 | }
48 |
49 | // Del delete
50 | func (c *CRepeat) Del(userID int64, check string) int64 {
51 | return c.cRepeatClient.Del(c.getKey(userID, check))
52 | }
53 |
--------------------------------------------------------------------------------
/internal/repository/README.md:
--------------------------------------------------------------------------------
1 | # Repository
2 |
3 | Repository,是数据访问层,负责访问 DB、MC、外部 HTTP 等接口,对上层屏蔽数据访问细节。
4 | 后续更换、升级ORM引擎,不影响业务逻辑。能提高测试效率,单元测试时,用Mock对象代替实际的数据库存取,可以成倍地提高测试用例运行速度。
5 | 应用 Repository 模式所带来的好处,远高于实现这个模式所增加的代码。只要项目分层,都应当使用这个模式。
6 | Repository是DDD中的概念,强调 Repository 是受Domain(本项目主要是Service)驱动的。
7 | 对 Model 层只能单表操作,每一个方法都有一个参数 `db *grom.DB` 实例,方便事务操作。
8 |
9 | 具体职责有:
10 | - SQL 拼接和 DB 访问逻辑
11 | - DB 的拆库分表逻辑
12 | - DB 的缓存读写逻辑
13 | - HTTP 接口调用逻辑
14 |
15 | > Tips: 如果是返回的列表,尽量返回map,方便service使用。
16 |
17 | 建议:
18 | - 推荐使用编写原生SQL
19 | - 禁止使用连表查询,好处是易扩展,比如分库分表
20 | - 逻辑部分在程序中进行处理
21 |
22 | 一个业务一个目录,每一个repo go文件对应一个表操作,比如用户是在user目录下,涉及用户相关的都可以放到这里,
23 | 根据不同的模块分离到不同的文件,同时又避免了单个文件func太多的问题。比如:
24 | - 用户基础服务- user_base_repo.go
25 | - 用户关注- user_follow_repo.go
26 | - 用户喜欢- user_like_repo.go
27 | - 用户评论- user_comment_repo.go
28 |
29 | ## 单元测试
30 |
31 | 关于数据库的单元测试可以用到的几个库:
32 | - go-sqlmock https://github.com/DATA-DOG/go-sqlmock 主要用来和数据库的交互操作:增删改
33 | - GoMock https://github.com/golang/mock
34 |
35 | ## Reference
36 | - https://github.com/realsangil/apimonitor/blob/fe1e9ef75dfbf021822d57ee242089167582934a/pkg/rsdb/repository.go
37 | - https://youtu.be/twcDf_Y2gXY?t=636
38 | - [Unit testing GORM with go-sqlmock in Go](https://medium.com/@rosaniline/unit-testing-gorm-with-go-sqlmock-in-go-93cbce1f6b5b)
39 | - [如何使用Sqlmock对GORM应用进行单元测试](https://1024casts.com/topics/R9re7QDaq8MnJoaXRZxdljbNA5BwoK)
40 |
--------------------------------------------------------------------------------
/internal/server/grpc.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/go-eagle/eagle/pkg/app"
7 | "github.com/go-eagle/eagle/pkg/transport/grpc"
8 | )
9 |
10 | // NewGRPCServer creates a gRPC server
11 | func NewGRPCServer(cfg *app.ServerConfig) *grpc.Server {
12 | grpcServer := grpc.NewServer(
13 | grpc.Network("tcp"),
14 | grpc.Address(":9090"),
15 | grpc.Timeout(3*time.Second),
16 | )
17 |
18 | // register biz service
19 | // v1.RegisterUserServiceServer(grpcServer, service.Svc.Users())
20 |
21 | return grpcServer
22 | }
23 |
--------------------------------------------------------------------------------
/internal/server/http.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "github.com/go-eagle/eagle/internal/routers"
5 | "github.com/go-eagle/eagle/pkg/app"
6 | "github.com/go-eagle/eagle/pkg/transport/http"
7 | )
8 |
9 | // NewHTTPServer creates a HTTP server
10 | func NewHTTPServer(c *app.ServerConfig) *http.Server {
11 | router := routers.NewRouter()
12 |
13 | srv := http.NewServer(
14 | http.WithAddress(c.Addr),
15 | http.WithReadTimeout(c.ReadTimeout),
16 | http.WithWriteTimeout(c.WriteTimeout),
17 | )
18 |
19 | srv.Handler = router
20 | // NOTE: register svc to http server
21 |
22 | return srv
23 | }
24 |
--------------------------------------------------------------------------------
/internal/service/README.md:
--------------------------------------------------------------------------------
1 | # service
2 |
3 | - 业务逻辑层,处于 `handler` 层和 `repository` 层之间
4 | - `service` 只能通过 `repository` 层获取数据
5 | - 面向接口编程
6 | - 依赖接口,不要依赖实现
7 | - 如果有事务处理,在这一层进行处理
8 | - 如果是调用的第三方服务,请不要加 `cache`, 避免缓存不一致(对方更新数据,这边无法知晓)
9 | - 由于 `service` 会被 `http` 或 `rpc` 调用,默认会提供 `http` 调用的,比如:`GetUserInfo()`,
10 | 如果 `rpc` 需要调用,可以对 `GetUserInfo()` 进行一层封装, 比如:`GetUser()`。
11 |
12 | ## Reference
13 |
14 | - https://github.com/qiangxue/go-rest-api
15 | - https://github.com/irahardianto/service-pattern-go
16 | - https://github.com/golang-standards/project-layout
17 | - https://www.youtube.com/watch?v=oL6JBUk6tj0
18 | - https://peter.bourgon.org/blog/2017/06/09/theory-of-modern-go.html
19 | - https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1
20 | - [按需写 service 服务层逻辑](https://www.5-wow.com/article/detail/89)
21 | - [Go 编程实战:如何组织代码、编写测试?](https://www.infoq.cn/article/4TAWp8YNYcVD4t046EGd)
22 | - https://github.com/sdgmf/go-project-sample
23 | - [Golang微服务最佳实践](https://sdgmf.github.io/goproject/)
24 | - [layout 常见大型 Web 项目分层](https://chai2010.cn/advanced-go-programming-book/ch5-web/ch5-07-layout-of-web-project.html)
25 | - [在 Golang 中尝试简洁架构](https://studygolang.com/articles/12909)
26 |
--------------------------------------------------------------------------------
/internal/service/common_service.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import "github.com/gin-gonic/gin"
4 |
5 | // GetUserID 返回用户id
6 | func GetUserID(c *gin.Context) uint64 {
7 | if c == nil {
8 | return 0
9 | }
10 |
11 | // uid 必须和 middleware/auth 中的 uid 命名一致
12 | if v, exists := c.Get("uid"); exists {
13 | uid, ok := v.(uint64)
14 | if !ok {
15 | return 0
16 | }
17 |
18 | return uid
19 | }
20 | return 0
21 | }
22 |
--------------------------------------------------------------------------------
/internal/service/service.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/go-eagle/eagle/internal/repository"
5 | )
6 |
7 | // Svc global var
8 | var Svc Service
9 |
10 | const (
11 | // DefaultLimit 默认分页数
12 | DefaultLimit = 50
13 |
14 | // MaxID 最大id
15 | MaxID = 0xffffffffffff
16 |
17 | // DefaultAvatar 默认头像 key
18 | DefaultAvatar = "default_avatar.png"
19 | )
20 |
21 | // Service define all service
22 | type Service interface {
23 | Users() UserService
24 | Relations() RelationService
25 | SMS() SMSService
26 | VCode() VCodeService
27 | }
28 |
29 | // service struct
30 | type service struct {
31 | repo repository.Repository
32 | }
33 |
34 | // New init service
35 | func New(repo repository.Repository) Service {
36 | return &service{
37 | repo: repo,
38 | }
39 | }
40 |
41 | func (s *service) Users() UserService {
42 | return newUsers(s)
43 | }
44 |
45 | func (s *service) Relations() RelationService {
46 | return newRelations(s)
47 | }
48 |
49 | func (s *service) SMS() SMSService {
50 | return newSMS(s)
51 | }
52 |
53 | func (s *service) VCode() VCodeService {
54 | return newVCode(s)
55 | }
56 |
--------------------------------------------------------------------------------
/internal/service/sms_service.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/pkg/errors"
5 | "github.com/qiniu/api.v7/auth"
6 | "github.com/qiniu/api.v7/sms"
7 |
8 | "github.com/go-eagle/eagle/internal/repository"
9 | )
10 |
11 | // SMSService define interface func
12 | type SMSService interface {
13 | SendSMS(phoneNumber string, verifyCode int) error
14 | }
15 |
16 | type smsService struct {
17 | repo repository.Repository
18 | }
19 |
20 | var _ SMSService = (*smsService)(nil)
21 |
22 | func newSMS(svc *service) *smsService {
23 | return &smsService{repo: svc.repo}
24 | }
25 |
26 | // Send 发送短信
27 | func (s *smsService) SendSMS(phoneNumber string, verifyCode int) error {
28 | // 校验参数的正确性
29 | if phoneNumber == "" || verifyCode == 0 {
30 | return errors.New("param phone or verify_code error")
31 | }
32 |
33 | // 调用第三方发送服务
34 | return sendViaQiNiu(phoneNumber, verifyCode)
35 | }
36 |
37 | // sendViaQiNiu 调用七牛短信服务
38 | func sendViaQiNiu(phoneNumber string, verifyCode int) error {
39 | accessKey := ""
40 | secretKey := ""
41 |
42 | mac := auth.New(accessKey, secretKey)
43 | manager := sms.NewManager(mac)
44 |
45 | args := sms.MessagesRequest{
46 | SignatureID: "",
47 | TemplateID: "",
48 | Mobiles: []string{phoneNumber},
49 | Parameters: map[string]interface{}{
50 | "code": verifyCode,
51 | },
52 | }
53 |
54 | ret, err := manager.SendMessage(args)
55 | if err != nil {
56 | return errors.Wrap(err, "send sms message error")
57 | }
58 |
59 | if len(ret.JobID) == 0 {
60 | return errors.New("send sms message job id error")
61 | }
62 |
63 | return nil
64 | }
65 |
--------------------------------------------------------------------------------
/internal/service/sms_service_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func Test_smsService_Send(t *testing.T) {
8 | type args struct {
9 | phoneNumber string
10 | verifyCode int
11 | }
12 | tests := []struct {
13 | name string
14 | args args
15 | wantErr bool
16 | }{
17 | // TODO: Add test cases.
18 | }
19 |
20 | s := New(nil)
21 | for _, tt := range tests {
22 | t.Run(tt.name, func(t *testing.T) {
23 | if err := s.SMS().SendSMS(tt.args.phoneNumber, tt.args.verifyCode); (err != nil) != tt.wantErr {
24 | t.Errorf("Send() error = %v, wantErr %v", err, tt.wantErr)
25 | }
26 | })
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/internal/service/trans.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "github.com/go-eagle/eagle/internal/model"
5 | )
6 |
7 | // TransferUserInput 转换输入字段
8 | type TransferUserInput struct {
9 | CurUser *model.UserBaseModel
10 | User *model.UserBaseModel
11 | UserStat *model.UserStatModel
12 | IsFollow int `json:"is_follow"`
13 | IsFans int `json:"is_fans"`
14 | }
15 |
16 | // TransferUser 组装数据并输出
17 | // 对外暴露的user结构,都应该经过此结构进行转换
18 | func TransferUser(input *TransferUserInput) *model.UserInfo {
19 | if input.User == nil {
20 | return &model.UserInfo{}
21 | }
22 |
23 | return &model.UserInfo{
24 | ID: input.User.ID,
25 | Username: input.User.Username,
26 | Avatar: input.User.Avatar, // todo: 转为url
27 | Sex: input.User.Sex,
28 | UserFollow: transferUserFollow(input),
29 | }
30 | }
31 |
32 | // transferUserFollow 转换用户关注相关字段
33 | func transferUserFollow(input *TransferUserInput) *model.UserFollow {
34 | followCount := 0
35 | if input.UserStat != nil {
36 | followCount = input.UserStat.FollowCount
37 | }
38 | followerCount := 0
39 | if input.UserStat != nil {
40 | followerCount = input.UserStat.FollowerCount
41 | }
42 |
43 | return &model.UserFollow{
44 | FollowNum: followCount,
45 | FansNum: followerCount,
46 | IsFollow: input.IsFollow,
47 | IsFans: input.IsFans,
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/internal/templates/README.md:
--------------------------------------------------------------------------------
1 | # 前端页面模板
--------------------------------------------------------------------------------
/internal/templates/error/404.html:
--------------------------------------------------------------------------------
1 | {{ define "css"}}
2 | {{ end }}
3 |
4 | {{define "content"}}
5 |
9 | 404 页面未找到
10 |
11 |
12 | 页面你去哪了呀~~~
13 |
14 |