Co-authored-by: Xinwei Xiong <86140903+cubxxw@users.noreply.github.com>tags/v0.1.0
| @@ -45,7 +45,7 @@ jobs: | |||
| # args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0 | |||
| # Optional: show only new issues if it's a pull request. The default value is `false`. | |||
| only-new-issues: true | |||
| # only-new-issues: true | |||
| # Optional:The mode to install golangci-lint. It can be 'binary' or 'goinstall'. | |||
| install-mode: "goinstall" | |||
| @@ -58,9 +58,9 @@ jobs: | |||
| run: | | |||
| make cover | |||
| # - name: Build source code for host platform | |||
| # run: | | |||
| # make build | |||
| - name: Build source code for host platform | |||
| run: | | |||
| # make build # skip build for now | |||
| - name: Collect Test Coverage File | |||
| uses: actions/upload-artifact@v1.0.0 | |||
| @@ -71,13 +71,15 @@ jobs: | |||
| - name: Set up Docker Buildx | |||
| uses: docker/setup-buildx-action@v1 | |||
| - name: Login to DockerHub | |||
| uses: docker/login-action@v1 | |||
| with: | |||
| username: ${{ env.DOCKERHUB_USERNAME }} | |||
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |||
| # skip for now | |||
| # - name: Login to DockerHub | |||
| # uses: docker/login-action@v1 | |||
| # with: | |||
| # username: ${{ env.DOCKERHUB_USERNAME }} | |||
| # password: ${{ secrets.DOCKERHUB_TOKEN }} | |||
| # - name: Build docker images for host arch and push images to registry | |||
| # run: | | |||
| # set -e | |||
| # make docker-push | |||
| # skip for now | |||
| # - name: Build docker images for host arch and push images to registry | |||
| # run: | | |||
| # set -e | |||
| # make docker-push | |||
| @@ -160,15 +160,15 @@ linters-settings: | |||
| - '*.Embedded' | |||
| - '*.External' | |||
| forbidigo: | |||
| # Forbid the following identifiers (identifiers are written using regexp): | |||
| forbid: | |||
| - ^print.*$ | |||
| - 'fmt\.Print.*' | |||
| - fmt.Println.* # too much log noise | |||
| - ginkgo\\.F.* # these are used just for local development | |||
| # Exclude godoc examples from forbidigo checks. Default is true. | |||
| exclude_godoc_examples: false | |||
| # forbidigo: | |||
| # # Forbid the following identifiers (identifiers are written using regexp): | |||
| # forbid: | |||
| # - ^print.*$ | |||
| # - 'fmt\.Print.*' | |||
| # - fmt.Println.* # too much log noise | |||
| # - ginkgo\\.F.* # these are used just for local development | |||
| # # Exclude godoc examples from forbidigo checks. Default is true. | |||
| # exclude_godoc_examples: false | |||
| funlen: | |||
| lines: 150 | |||
| statements: 50 | |||
| @@ -296,7 +296,7 @@ linters-settings: | |||
| gofumpt: | |||
| # Select the Go version to target. The default is `1.18`. | |||
| lang-version: "1.18" | |||
| lang-version: "1.20" | |||
| # Choose whether or not to use the extra rules that are disabled | |||
| # by default | |||
| @@ -409,7 +409,7 @@ linters-settings: | |||
| gosimple: | |||
| # Select the Go version to target. The default is '1.13'. | |||
| go: "1.18" | |||
| go: "1.20" | |||
| # https://staticcheck.io/docs/options#checks | |||
| checks: [ "all" ] | |||
| @@ -433,14 +433,14 @@ linters-settings: | |||
| disable: | |||
| - shadow | |||
| disable-all: false | |||
| depguard: | |||
| list-type: blacklist | |||
| include-go-root: false | |||
| packages: | |||
| - github.com/Sirupsen/logrus | |||
| packages-with-error-message: | |||
| # specify an error message to output when a blacklisted package is used | |||
| - github.com/Sirupsen/logrus: "logging is allowed only by logutils.Log" | |||
| # depguard: | |||
| # list-type: blacklist | |||
| # include-go-root: false | |||
| # packages: | |||
| # - github.com/Sirupsen/logrus | |||
| # packages-with-error-message: | |||
| # # specify an error message to output when a blacklisted package is used | |||
| # - github.com/Sirupsen/logrus: "logging is allowed only by logutils.Log" | |||
| ifshort: | |||
| # Maximum length of variable declaration measured in number of lines, after which linter won't suggest using short syntax. | |||
| # Has higher priority than max-decl-chars. | |||
| @@ -722,7 +722,7 @@ linters: | |||
| - bodyclose | |||
| - cyclop | |||
| - deadcode | |||
| - depguard | |||
| # - depguard | |||
| - dogsled | |||
| - dupl | |||
| - durationcheck | |||
| @@ -730,10 +730,10 @@ linters: | |||
| - errorlint | |||
| - exhaustive | |||
| - exportloopref | |||
| - forbidigo | |||
| # - forbidigo | |||
| - funlen | |||
| #- gci | |||
| - gochecknoinits | |||
| # - gci | |||
| # - gochecknoinits | |||
| - gocognit | |||
| - goconst | |||
| - gocyclo | |||
| @@ -813,8 +813,9 @@ issues: | |||
| # Excluding configuration per-path, per-linter, per-text and per-source | |||
| exclude-rules: | |||
| - linters: | |||
| - errorlint | |||
| path: (pkg/validator/.*)\.go | |||
| - golint | |||
| path: (internal/api/.*)\.go # exclude golint for internal/api/... files | |||
| - linters: | |||
| - revive | |||
| path: (log/.*)\.go | |||
| @@ -227,7 +227,7 @@ generate: | |||
| .PHONY: lint | |||
| lint: tools.verify.golangci-lint | |||
| @echo "===========> Run golangci to lint source codes" | |||
| @cd $(SERVER_DIR) && $(TOOLS_DIR)/golangci-lint run -c $(ROOT_DIR)/.golangci.yml $(ROOT_DIR)/$(SERVER_DIR)/... | |||
| @cd $(SERVER_DIR) && $(TOOLS_DIR)/golangci-lint run -c $(ROOT_DIR)/.golangci.yml | |||
| ## test: Run unit test | |||
| .PHONY: test | |||
| @@ -237,7 +237,7 @@ test: | |||
| ## cover: Run unit test with coverage. | |||
| .PHONY: cover | |||
| cover: test | |||
| @cd $(SERVER_DIR) && go test -cover | |||
| @cd $(SERVER_DIR) && go test -coverprofile=$(TMP_DIR)/coverage.out | |||
| ## docker-build: Build docker image with the manager. | |||
| .PHONY: docker-build | |||
| @@ -30,7 +30,7 @@ | |||
| ## 🧩 Awesome features | |||
| 1. [OpenKF](https://https://github.com/OpenIMSDK/OpenKF) is an opensource customer service system based on [OpenIM](https://https://github.com/OpenIMSDK). | |||
| 1. [OpenKF](https://github.com/OpenIMSDK/OpenKF) is an opensource customer service system based on [OpenIM](https://github.com/OpenIMSDK). | |||
| 2. Support LLM(Local Knowledgebase) customer service. | |||
| 3. Support multi-channel customer service, and easy to integrate with third-party systems. | |||
| 4. Easy to deploy and secondary development. | |||
| @@ -30,7 +30,7 @@ | |||
| ## 🧩 特性 | |||
| 1. [OpenKF](https://https://github.com/OpenIMSDK/OpenKF) 是基于 [OpenIM](https://https://github.com/OpenIMSDK) 的开源客服系统。 | |||
| 1. [OpenKF](https://github.com/OpenIMSDK/OpenKF) 是基于 [OpenIM](https://github.com/OpenIMSDK) 的开源客服系统。 | |||
| 2. 支持 LLM(本地知识库)客服。 | |||
| 3. 支持多渠道客服,并易于与第三方系统集成。 | |||
| 4. 易于部署和二次开发。 | |||
| @@ -23,6 +23,7 @@ import ( | |||
| "strings" | |||
| ) | |||
| // HookGenerator is a generator for hooks | |||
| type HookGenerator struct { | |||
| buf *bytes.Buffer | |||
| config *config | |||
| @@ -34,6 +35,7 @@ type config struct { | |||
| UrlPattern string | |||
| } | |||
| // NewHookGenerator returns a new HookGenerator | |||
| func NewHookGenerator(hookName, urlPattern, savePath string) *HookGenerator { | |||
| return &HookGenerator{ | |||
| buf: bytes.NewBuffer(nil), | |||
| @@ -45,6 +47,7 @@ func NewHookGenerator(hookName, urlPattern, savePath string) *HookGenerator { | |||
| } | |||
| } | |||
| // Generate init the hook | |||
| func (g *HookGenerator) Generate() *HookGenerator { | |||
| if err := hookTemplate.Execute(g.buf, g.config); err != nil { | |||
| panic(err) | |||
| @@ -53,6 +56,7 @@ func (g *HookGenerator) Generate() *HookGenerator { | |||
| return g | |||
| } | |||
| // Format format the generated code | |||
| func (g *HookGenerator) Format() *HookGenerator { | |||
| formatOut, err := format.Source(g.buf.Bytes()) | |||
| if err != nil { | |||
| @@ -63,6 +67,7 @@ func (g *HookGenerator) Format() *HookGenerator { | |||
| return g | |||
| } | |||
| // Flush write the generated code to file | |||
| func (g *HookGenerator) Flush() { | |||
| filename := fmt.Sprintf("gen_%s_hook.go", strings.ToLower(g.config.HookName)) | |||
| if err := ioutil.WriteFile( | |||
| @@ -42,7 +42,7 @@ require ( | |||
| github.com/go-sql-driver/mysql v1.7.0 // indirect | |||
| github.com/goccy/go-json v0.10.2 // indirect | |||
| github.com/google/uuid v1.3.0 // indirect | |||
| github.com/gookit/color v1.5.2 // indirect | |||
| github.com/gookit/color v1.5.3 // indirect | |||
| github.com/hashicorp/hcl v1.0.0 // indirect | |||
| github.com/huandu/xstrings v1.3.3 // indirect | |||
| github.com/jinzhu/inflection v1.0.0 // indirect | |||
| @@ -64,9 +64,11 @@ require ( | |||
| github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | |||
| github.com/modern-go/reflect2 v1.0.2 // indirect | |||
| github.com/muesli/reflow v0.3.0 // indirect | |||
| github.com/onsi/gomega v1.27.6 // indirect | |||
| github.com/pelletier/go-toml/v2 v2.0.8 // indirect | |||
| github.com/pkg/errors v0.9.1 // indirect | |||
| github.com/rivo/uniseg v0.2.0 // indirect | |||
| github.com/rogpeppe/go-internal v1.10.0 // indirect | |||
| github.com/rs/xid v1.5.0 // indirect | |||
| github.com/spf13/afero v1.9.5 // indirect | |||
| github.com/spf13/cast v1.5.1 // indirect | |||
| @@ -79,6 +81,7 @@ require ( | |||
| github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect | |||
| golang.org/x/arch v0.3.0 // indirect | |||
| golang.org/x/crypto v0.10.0 // indirect | |||
| golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect | |||
| golang.org/x/net v0.11.0 // indirect | |||
| golang.org/x/sys v0.9.0 // indirect | |||
| golang.org/x/term v0.9.0 // indirect | |||
| @@ -179,8 +179,9 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ | |||
| github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= | |||
| github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= | |||
| github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= | |||
| github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI= | |||
| github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg= | |||
| github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE= | |||
| github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE= | |||
| github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | |||
| github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | |||
| github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= | |||
| @@ -257,7 +258,8 @@ github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKt | |||
| github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | |||
| github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= | |||
| github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= | |||
| github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= | |||
| github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= | |||
| github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= | |||
| github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= | |||
| github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= | |||
| github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | |||
| @@ -272,7 +274,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ | |||
| github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= | |||
| github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | |||
| github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | |||
| github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= | |||
| github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= | |||
| github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= | |||
| github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= | |||
| github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= | |||
| github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= | |||
| @@ -352,8 +355,9 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 | |||
| golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | |||
| golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= | |||
| golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= | |||
| golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= | |||
| golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= | |||
| golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= | |||
| golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= | |||
| golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | |||
| golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | |||
| golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | |||
| @@ -36,6 +36,7 @@ func SendCode(c *gin.Context) { | |||
| err := c.ShouldBindJSON(¶ms) | |||
| if err != nil { | |||
| response.FailWithCode(common.INVALID_PARAMS, c) | |||
| return | |||
| } | |||
| @@ -44,6 +45,7 @@ func SendCode(c *gin.Context) { | |||
| if err != nil { | |||
| log.Debug("SendCode error: ", err) | |||
| response.FailWithCode(common.ERROR, c) | |||
| return | |||
| } | |||
| @@ -14,6 +14,7 @@ | |||
| package common | |||
| // Code. | |||
| const ( | |||
| SUCCESS = 200 | |||
| ERROR = 500 | |||
| @@ -14,17 +14,19 @@ | |||
| package common | |||
| var Msg = map[int]string{ | |||
| // msg is a mapping of message. | |||
| var msg = map[int]string{ | |||
| SUCCESS: "success", | |||
| ERROR: "error", | |||
| INVALID_PARAMS: "request params error", | |||
| } | |||
| // GetMsg get the message by code. | |||
| func GetMsg(code int) string { | |||
| msg, ok := Msg[code] | |||
| m, ok := msg[code] | |||
| if ok { | |||
| return msg | |||
| return m | |||
| } | |||
| return Msg[ERROR] | |||
| return msg[ERROR] | |||
| } | |||
| @@ -21,12 +21,14 @@ import ( | |||
| "github.com/gin-gonic/gin" | |||
| ) | |||
| // Response is a common struct for response | |||
| type Response struct { | |||
| Code int `json:"code"` | |||
| Msg string `json:"msg"` | |||
| Data interface{} `json:"data"` | |||
| } | |||
| // NewResponse returns a new Response | |||
| func NewResponse(code int, msg string, data interface{}, c *gin.Context) { | |||
| c.JSON(http.StatusOK, &Response{ | |||
| Code: code, | |||
| @@ -35,34 +37,42 @@ func NewResponse(code int, msg string, data interface{}, c *gin.Context) { | |||
| }) | |||
| } | |||
| // Success returns a success response | |||
| func Success(c *gin.Context) { | |||
| NewResponse(common.SUCCESS, common.GetMsg(common.SUCCESS), nil, c) | |||
| } | |||
| // SuccessWithData returns a success response with data | |||
| func SuccessWithData(data interface{}, c *gin.Context) { | |||
| NewResponse(common.SUCCESS, common.GetMsg(common.SUCCESS), data, c) | |||
| } | |||
| // SuccessWithCode returns a success response with code | |||
| func SuccessWithCode(code int, c *gin.Context) { | |||
| NewResponse(code, common.GetMsg(code), nil, c) | |||
| } | |||
| // SuccessWithAll returns a success response with code and data | |||
| func SuccessWithAll(code int, data interface{}, c *gin.Context) { | |||
| NewResponse(code, common.GetMsg(code), data, c) | |||
| } | |||
| // Fail returns a fail response | |||
| func Fail(c *gin.Context) { | |||
| NewResponse(common.ERROR, common.GetMsg(common.ERROR), nil, c) | |||
| } | |||
| // FailWithData returns a fail response with data | |||
| func FailWithData(data interface{}, c *gin.Context) { | |||
| NewResponse(common.ERROR, common.GetMsg(common.ERROR), data, c) | |||
| } | |||
| // FailWithCode returns a fail response with code | |||
| func FailWithCode(code int, c *gin.Context) { | |||
| NewResponse(code, common.GetMsg(code), nil, c) | |||
| } | |||
| // FailWithAll returns a fail response with code and data | |||
| func FailWithAll(code int, data interface{}, c *gin.Context) { | |||
| NewResponse(code, common.GetMsg(code), data, c) | |||
| } | |||
| @@ -14,8 +14,10 @@ | |||
| package config | |||
| // Config global config instance | |||
| var Config *config | |||
| // ConfigInit init config | |||
| func ConfigInit(configPath string) { | |||
| // init viper | |||
| initViper(configPath) | |||
| @@ -81,23 +83,27 @@ type config struct { | |||
| Email Email | |||
| } | |||
| // App config | |||
| type App struct { | |||
| Version string `mapstructure:"version"` | |||
| Debug bool `mapstructure:"debug"` | |||
| LogFile string `mapstructure:"log_file"` | |||
| } | |||
| // JWT config | |||
| type JWT struct { | |||
| Secret string `mapstructure:"secret"` | |||
| Issuer string `mapstructure:"issuer"` | |||
| ExpireDays int `mapstructure:"expire_days"` | |||
| } | |||
| // Server config | |||
| type Server struct { | |||
| Ip string `mapstructure:"ip"` | |||
| Port int `mapstructure:"port"` | |||
| } | |||
| // Mysql config | |||
| type Mysql struct { | |||
| Ip string `mapstructure:"ip"` | |||
| Port int `mapstructure:"port"` | |||
| @@ -109,6 +115,7 @@ type Mysql struct { | |||
| MaxIdleConns int `mapstructure:"max_idle_conns"` | |||
| } | |||
| // Redis config | |||
| type Redis struct { | |||
| Ip string `mapstructure:"ip"` | |||
| Port int `mapstructure:"port"` | |||
| @@ -116,6 +123,7 @@ type Redis struct { | |||
| Database int `mapstructure:"database"` | |||
| } | |||
| // Minio config | |||
| type Minio struct { | |||
| Ip string `mapstructure:"ip"` | |||
| Port int `mapstructure:"port"` | |||
| @@ -126,6 +134,7 @@ type Minio struct { | |||
| Location string `mapstructure:"location"` | |||
| } | |||
| // Email config | |||
| type Email struct { | |||
| Host string `mapstructure:"host"` | |||
| Port int `mapstructure:"port"` | |||
| @@ -32,36 +32,49 @@ func initViper(configPath string) { | |||
| fmt.Println("Load ok") | |||
| } | |||
| // get config | |||
| // GetInterface get interface config | |||
| func GetInterface(key string) interface{} { | |||
| return viper.Get(key) | |||
| } | |||
| // GetString get string config | |||
| func GetString(key string) string { | |||
| return viper.GetString(key) | |||
| } | |||
| // GetInt get int config | |||
| func GetInt(key string) int { | |||
| return viper.GetInt(key) | |||
| } | |||
| // GetBool get bool config | |||
| func GetBool(key string) bool { | |||
| return viper.GetBool(key) | |||
| } | |||
| // get config or use default | |||
| // GetStringOrDefault get string config or use default | |||
| func GetStringOrDefault(key string, defaultValue string) string { | |||
| if viper.IsSet(key) { | |||
| return viper.GetString(key) | |||
| } | |||
| return defaultValue | |||
| } | |||
| // GetIntOrDefault get int config or use default | |||
| func GetIntOrDefault(key string, defaultValue int) int { | |||
| if viper.IsSet(key) { | |||
| return viper.GetInt(key) | |||
| } | |||
| return defaultValue | |||
| } | |||
| // GetBoolOrDefault get bool config or use default | |||
| func GetBoolOrDefault(key string, defaultValue bool) bool { | |||
| if viper.IsSet(key) { | |||
| return viper.GetBool(key) | |||
| } | |||
| return defaultValue | |||
| } | |||
| @@ -25,6 +25,7 @@ import ( | |||
| // todo: use email connection pool to reduce the cost of creating a connection | |||
| // link: https://github.com/jordan-wright/email#a-pool-of-reusable-connections | |||
| // SendEmail send email | |||
| func SendEmail(to string, subject string, body string) error { | |||
| email := email.NewEmail() | |||
| @@ -45,6 +46,7 @@ func SendEmail(to string, subject string, body string) error { | |||
| return nil | |||
| } | |||
| // SendHtmlEmail send html email | |||
| func SendHtmlEmail(to string, subject string, html string) error { | |||
| email := email.NewEmail() | |||
| @@ -28,6 +28,7 @@ import ( | |||
| var _minioClient *minio.Client | |||
| var _bucket string | |||
| // InitMinio init minio client | |||
| func InitMinio() { | |||
| endpoint := fmt.Sprintf("%s:%d", config.Config.Minio.Ip, config.Config.Minio.Port) | |||
| accessKeyID := config.Config.Minio.AccessKeyId | |||
| @@ -36,7 +37,7 @@ func InitMinio() { | |||
| _bucket = config.Config.Minio.Bucket | |||
| // Initialize _minioClient | |||
| _minioClient, err := minio.New( | |||
| minioClient, err := minio.New( | |||
| endpoint, | |||
| &minio.Options{ | |||
| Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), | |||
| @@ -48,12 +49,12 @@ func InitMinio() { | |||
| } | |||
| // create bucket if not exists | |||
| exists, err := _minioClient.BucketExists(context.Background(), _bucket) | |||
| exists, err := minioClient.BucketExists(context.Background(), _bucket) | |||
| if err != nil { | |||
| log.Panicf("Minio", err.Error(), " Open Bucket failed ", endpoint) | |||
| } | |||
| if !exists { | |||
| if err = _minioClient.MakeBucket( | |||
| if err = minioClient.MakeBucket( | |||
| context.Background(), | |||
| _bucket, | |||
| minio.MakeBucketOptions{Region: location, ObjectLocking: true}, | |||
| @@ -61,14 +62,20 @@ func InitMinio() { | |||
| log.Panicf("Minio", err.Error(), " Open failed ", endpoint) | |||
| } | |||
| } | |||
| _minioClient = minioClient | |||
| } | |||
| // PutObject put object to minio | |||
| func PutObject(objectName string, r io.Reader, objectSize int64) error { | |||
| _, err := _minioClient.PutObject(context.Background(), _bucket, objectName, r, objectSize, minio.PutObjectOptions{ContentType: "application/octet-stream"}) | |||
| return err | |||
| } | |||
| // GetObject get object from minio | |||
| func GetObject(objectName string) (io.Reader, error) { | |||
| object, err := _minioClient.GetObject(context.Background(), _bucket, objectName, minio.GetObjectOptions{}) | |||
| return object, err | |||
| } | |||
| @@ -27,12 +27,14 @@ import ( | |||
| var d *gorm.DB | |||
| type Writer struct{} | |||
| type writer struct{} | |||
| func (w Writer) Printf(format string, args ...interface{}) { | |||
| // Write implement log writer interface | |||
| func (w writer) Printf(format string, args ...interface{}) { | |||
| fmt.Printf(format, args...) | |||
| } | |||
| // InitMysqlDB init mysql connection. | |||
| func InitMysqlDB() { | |||
| // try to use default database [mysql] | |||
| dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", | |||
| @@ -71,7 +73,7 @@ func InitMysqlDB() { | |||
| ) | |||
| logger := logger.New( | |||
| Writer{}, | |||
| writer{}, | |||
| logger.Config{ | |||
| IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger | |||
| Colorful: true, // Disable color | |||
| @@ -103,21 +105,24 @@ func InitMysqlDB() { | |||
| log.Info("Mysql", "connect ok", dsn) | |||
| } | |||
| // get mysql connection. | |||
| // GetMysqlDB get mysql connection. | |||
| func GetMysqlDB() *gorm.DB { | |||
| return d | |||
| } | |||
| // CloseMysqlDB close mysql connection. | |||
| func CloseMysqlDB() { | |||
| if d != nil { | |||
| sqlDB, err := d.DB() | |||
| if d == nil { | |||
| return | |||
| } | |||
| sqlDB, err := d.DB() | |||
| if err != nil { | |||
| log.Error("Mysql", err.Error(), " db.DB() failed ") | |||
| } else { | |||
| err = sqlDB.Close() | |||
| if err != nil { | |||
| log.Error("Mysql", err.Error(), " db.DB() failed ") | |||
| } else { | |||
| err = sqlDB.Close() | |||
| if err != nil { | |||
| log.Error("Mysql", err.Error(), " sqlDB.Close() failed ") | |||
| } | |||
| log.Error("Mysql", err.Error(), " sqlDB.Close() failed ") | |||
| } | |||
| } | |||
| } | |||
| @@ -25,6 +25,7 @@ import ( | |||
| var r *redis.Client | |||
| // InitRedisDB init redis client | |||
| func InitRedisDB() { | |||
| r = redis.NewClient(&redis.Options{ | |||
| Addr: fmt.Sprintf("%s:%d", config.Config.Redis.Ip, config.Config.Redis.Port), | |||
| @@ -39,10 +40,12 @@ func InitRedisDB() { | |||
| } | |||
| } | |||
| // GetRedis get redis client instance | |||
| func GetRedis() *redis.Client { | |||
| return r | |||
| } | |||
| // CloseRedis close redis client instance | |||
| func CloseRedis() { | |||
| if r != nil { | |||
| err := r.Close() | |||
| @@ -20,6 +20,7 @@ import ( | |||
| "github.com/gin-gonic/gin" | |||
| ) | |||
| // EnableCROS enable cros | |||
| func EnableCROS() gin.HandlerFunc { | |||
| return func(c *gin.Context) { | |||
| // c.Writer.Header().Set("Access-Control-Allow-Origin", "*") | |||
| @@ -27,19 +27,23 @@ func init() { | |||
| fmt.Println("RegisterHook", "Register Hook[GlobalHook] success...") | |||
| } | |||
| // GlobalHook implement urltrie.Hook | |||
| type GlobalHook struct { | |||
| urltrie.Hook | |||
| } | |||
| // Pattern return pattern | |||
| func (h GlobalHook) Pattern() string { | |||
| return "/*" | |||
| } | |||
| // BeforeRun do something before controller run | |||
| func (h GlobalHook) BeforeRun(c *gin.Context) { | |||
| log.Debugf("GlobalHook", "path: %v", c.Request.URL.Path) | |||
| c.Next() | |||
| } | |||
| // AfterRun do something after controller run | |||
| func (h GlobalHook) AfterRun(c *gin.Context) { | |||
| c.Next() | |||
| } | |||
| @@ -14,6 +14,6 @@ | |||
| package hooks | |||
| // Manual import hooks packages and do init | |||
| // InitHooks manual import hooks packages and do init | |||
| func InitHooks() { | |||
| } | |||
| @@ -27,19 +27,23 @@ func init() { | |||
| fmt.Println("RegisterHook", "Register Hook[MailHook] success...") | |||
| } | |||
| // MailHook implement urltrie.Hook | |||
| type MailHook struct { | |||
| urltrie.Hook | |||
| } | |||
| // Pattern return pattern | |||
| func (h MailHook) Pattern() string { | |||
| return "/api/v1/register/email/code" | |||
| } | |||
| // BeforeRun do something before controller run | |||
| func (h MailHook) BeforeRun(c *gin.Context) { | |||
| log.Debugf("GlobalHook", "path: %v", c.Request.URL.Path) | |||
| c.Next() | |||
| } | |||
| // AfterRun do something after controller run | |||
| func (h MailHook) AfterRun(c *gin.Context) { | |||
| c.Next() | |||
| } | |||
| @@ -28,12 +28,12 @@ func init() { | |||
| hookTrie = NewTrie() | |||
| } | |||
| // Register url & hook to trie | |||
| // RegisterHook register url & hook to trie | |||
| func RegisterHook(hook Hook) { | |||
| hookTrie.Insert(hook.Pattern(), hook) | |||
| } | |||
| // Enable hook for interceptor | |||
| // RunHook enable hook for interceptor | |||
| func RunHook() gin.HandlerFunc { | |||
| return func(c *gin.Context) { | |||
| raw := c.Request.URL.Path | |||
| @@ -48,6 +48,7 @@ func RunHook() gin.HandlerFunc { | |||
| hooks, ok := hookTrie.Match(path) | |||
| if !ok { | |||
| c.Next() | |||
| return | |||
| } | |||
| @@ -34,7 +34,7 @@ type Hook interface { | |||
| AfterRun(c *gin.Context) | |||
| } | |||
| const WILDCARD = "*" | |||
| const _wildcard = "*" | |||
| type node struct { | |||
| children map[string]*node | |||
| @@ -46,10 +46,12 @@ type node struct { | |||
| isEnd bool | |||
| } | |||
| // Trie is a tree for url | |||
| type Trie struct { | |||
| root *node | |||
| } | |||
| // NewTrie returns a new Trie | |||
| func NewTrie() *Trie { | |||
| return &Trie{ | |||
| root: &node{ | |||
| @@ -58,7 +60,7 @@ func NewTrie() *Trie { | |||
| } | |||
| } | |||
| // Insert url with hooks | |||
| // Insert insert url with hooks | |||
| func (t *Trie) Insert(url string, hooks ...Hook) { | |||
| current := t.root | |||
| @@ -76,7 +78,7 @@ func (t *Trie) Insert(url string, hooks ...Hook) { | |||
| data: part, | |||
| } | |||
| // match wildcard | |||
| if part == WILDCARD { | |||
| if part == _wildcard { | |||
| child.isWildcard = true | |||
| } | |||
| @@ -92,7 +94,7 @@ func (t *Trie) Insert(url string, hooks ...Hook) { | |||
| current.hooks = append(current.hooks, hooks...) | |||
| } | |||
| // Match url with hooks | |||
| // Match match url with hooks | |||
| func (t *Trie) Match(url string) ([]Hook, bool) { | |||
| current := t.root | |||
| @@ -110,36 +112,25 @@ func (t *Trie) Match(url string) ([]Hook, bool) { | |||
| } | |||
| if len(stack) == 0 { | |||
| if len(matchedValues) > 0 { | |||
| return matchedValues, true | |||
| } else { | |||
| return matchedValues, false | |||
| } | |||
| return matchedValues, len(matchedValues) > 0 | |||
| } | |||
| // get current level length | |||
| levelLen := len(stack) | |||
| for i := 0; i < levelLen; i++ { | |||
| // pop | |||
| current = stack[0] | |||
| stack = stack[1:] | |||
| current, stack = stack[0], stack[1:] | |||
| if current.isEnd { | |||
| if current.isWildcard || current.data == part { | |||
| // Match the last node, append the values | |||
| matchedValues = append(matchedValues, current.hooks...) | |||
| } | |||
| continue | |||
| } | |||
| // find wildcard node | |||
| if current.isWildcard { | |||
| for _, child := range current.children { | |||
| stack = append(stack, child) | |||
| } | |||
| continue | |||
| } | |||
| // find expect node | |||
| if current.data == part { | |||
| // find wildcard node or expect node | |||
| if current.isWildcard || current.data == part { | |||
| for _, child := range current.children { | |||
| stack = append(stack, child) | |||
| } | |||
| @@ -147,9 +138,5 @@ func (t *Trie) Match(url string) ([]Hook, bool) { | |||
| } | |||
| } | |||
| if len(matchedValues) > 0 { | |||
| return matchedValues, true | |||
| } else { | |||
| return matchedValues, false | |||
| } | |||
| return matchedValues, len(matchedValues) > 0 | |||
| } | |||
| @@ -22,17 +22,20 @@ import ( | |||
| "github.com/gin-gonic/gin" | |||
| ) | |||
| // EnableAuth enable auth middleware | |||
| func EnableAuth() gin.HandlerFunc { | |||
| return func(c *gin.Context) { | |||
| token := c.GetHeader("Authorization") | |||
| if token == "" || strings.Fields(token)[0] != "Bearer" { | |||
| c.AbortWithStatus(http.StatusUnauthorized) | |||
| return | |||
| } | |||
| _, err := utils.ParseJwtToken(token) | |||
| if err != nil { | |||
| c.AbortWithStatus(http.StatusUnauthorized) | |||
| return | |||
| } | |||
| @@ -16,6 +16,7 @@ package models | |||
| import "time" | |||
| // Model base model | |||
| type Model struct { | |||
| ID uint `gorm:"primary_key" json:"id"` | |||
| CreatedAt time.Time `json:"created_at"` | |||
| @@ -14,6 +14,7 @@ | |||
| package models | |||
| // User user model | |||
| type User struct { | |||
| Model | |||
| Username string `json:"username" gorm:"type:varchar(20);not null;unique"` | |||
| @@ -14,6 +14,7 @@ | |||
| package param | |||
| // SendToParams register params for email. | |||
| type SendToParams struct { | |||
| Email string `json:"email" binding:"required"` | |||
| } | |||
| @@ -15,7 +15,7 @@ | |||
| package router | |||
| import ( | |||
| _ "github.com/OpenIMSDK/OpenKF/server/docs" | |||
| _ "github.com/OpenIMSDK/OpenKF/server/docs" //nolint:golint | |||
| swaggerFiles "github.com/swaggo/files" | |||
| ginSwagger "github.com/swaggo/gin-swagger" | |||
| @@ -26,6 +26,7 @@ import ( | |||
| "github.com/gin-gonic/gin" | |||
| ) | |||
| // InitRouter init router | |||
| func InitRouter() *gin.Engine { | |||
| if config.GetString("app.debug") == "true" { | |||
| gin.SetMode(gin.DebugMode) | |||
| @@ -23,6 +23,7 @@ import ( | |||
| "gorm.io/gorm" | |||
| ) | |||
| // Service service | |||
| type Service struct { | |||
| // Context | |||
| Ctx context.Context | |||
| @@ -32,6 +33,7 @@ type Service struct { | |||
| Cache *redis.Client | |||
| } | |||
| // NewService return new service with context | |||
| func NewService() *Service { | |||
| return &Service{ | |||
| Ctx: context.Background(), | |||
| @@ -40,6 +42,7 @@ func NewService() *Service { | |||
| } | |||
| } | |||
| // NewServiceWithGin return new service with gin context | |||
| func NewServiceWithGin(c *gin.Context) *Service { | |||
| return &Service{ | |||
| Ctx: c.Request.Context(), | |||
| @@ -22,6 +22,7 @@ import ( | |||
| "github.com/OpenIMSDK/OpenKF/server/pkg/log" | |||
| ) | |||
| // SendCode send code to email | |||
| func (svc *Service) SendCode(email string) (err error) { | |||
| // check the code is exist | |||
| cmd := svc.Cache.Get(svc.Ctx, "code:"+email) | |||
| @@ -41,6 +42,7 @@ func (svc *Service) SendCode(email string) (err error) { | |||
| return err | |||
| } | |||
| // Test test | |||
| func (svc *Service) Test() { | |||
| log.Info("test...") | |||
| } | |||
| @@ -40,5 +40,6 @@ func (hook *filelineHook) Fire(entry *logrus.Entry) error { | |||
| s = i[len(i)-3] + i[len(i)-2] + i[len(i)-1] + ":" + utils.IntToString(line) | |||
| } | |||
| entry.Data["FilePath"] = s | |||
| return nil | |||
| } | |||
| @@ -29,6 +29,7 @@ import ( | |||
| var _logger *logrus.Logger | |||
| // InitLogger init logger | |||
| func InitLogger() { | |||
| _logger = loggerInit() | |||
| } | |||
| @@ -69,12 +70,13 @@ func loggerInit() *logrus.Logger { | |||
| return logger | |||
| } | |||
| // NewLfsHook add fileline hook | |||
| func NewLfsHook(rotationTime time.Duration, maxRemainNum uint) logrus.Hook { | |||
| lfsHook := lfshook.NewHook(lfshook.WriterMap{ | |||
| logrus.DebugLevel: initRotateLogs(rotationTime, maxRemainNum, "all"), | |||
| logrus.InfoLevel: initRotateLogs(rotationTime, maxRemainNum, "all"), | |||
| logrus.WarnLevel: initRotateLogs(rotationTime, maxRemainNum, "all"), | |||
| logrus.ErrorLevel: initRotateLogs(rotationTime, maxRemainNum, "all"), | |||
| logrus.DebugLevel: initRotateLogs(rotationTime, maxRemainNum), | |||
| logrus.InfoLevel: initRotateLogs(rotationTime, maxRemainNum), | |||
| logrus.WarnLevel: initRotateLogs(rotationTime, maxRemainNum), | |||
| logrus.ErrorLevel: initRotateLogs(rotationTime, maxRemainNum), | |||
| }, &nested.Formatter{ | |||
| TimestampFormat: "2006-01-02 15:04:05.000", | |||
| HideKeys: false, | |||
| @@ -84,9 +86,9 @@ func NewLfsHook(rotationTime time.Duration, maxRemainNum uint) logrus.Hook { | |||
| return lfsHook | |||
| } | |||
| func initRotateLogs(rotationTime time.Duration, maxRemainNum uint, level string) *rotatelogs.RotateLogs { | |||
| func initRotateLogs(rotationTime time.Duration, maxRemainNum uint) *rotatelogs.RotateLogs { | |||
| writer, err := rotatelogs.New( | |||
| config.Config.App.LogFile+level+".%Y%m%d%H%M", | |||
| config.Config.App.LogFile+".%Y%m%d%H%M", | |||
| rotatelogs.WithRotationTime(rotationTime), | |||
| rotatelogs.WithRotationCount(maxRemainNum), | |||
| ) | |||
| @@ -97,52 +99,61 @@ func initRotateLogs(rotationTime time.Duration, maxRemainNum uint, level string) | |||
| } | |||
| } | |||
| // GetLogger get logger instance | |||
| func GetLogger() *logrus.Logger { | |||
| return _logger | |||
| } | |||
| // Info log info | |||
| func Info(Operation string, args ...interface{}) { | |||
| _logger.WithFields(logrus.Fields{ | |||
| "Operation": Operation, | |||
| }).Infoln(args...) | |||
| } | |||
| // Error log error | |||
| func Error(Operation string, args ...interface{}) { | |||
| _logger.WithFields(logrus.Fields{ | |||
| "Operation": Operation, | |||
| }).Errorln(args...) | |||
| } | |||
| // Debug log debug | |||
| func Debug(Operation string, args ...interface{}) { | |||
| _logger.WithFields(logrus.Fields{ | |||
| "Operation": Operation, | |||
| }).Debugln(args...) | |||
| } | |||
| // Panic log panic | |||
| func Panic(Operation string, args ...interface{}) { | |||
| _logger.WithFields(logrus.Fields{ | |||
| "Operation": Operation, | |||
| }).Panicln(args...) | |||
| } | |||
| // Infof log info with format | |||
| func Infof(Operation string, format string, args ...interface{}) { | |||
| _logger.WithFields(logrus.Fields{ | |||
| "Operation": Operation, | |||
| }).Infof(format, args...) | |||
| } | |||
| // Errorf log error with format | |||
| func Errorf(Operation string, format string, args ...interface{}) { | |||
| _logger.WithFields(logrus.Fields{ | |||
| "Operation": Operation, | |||
| }).Errorf(format, args...) | |||
| } | |||
| // Debugf log debug with format | |||
| func Debugf(Operation string, format string, args ...interface{}) { | |||
| _logger.WithFields(logrus.Fields{ | |||
| "Operation": Operation, | |||
| }).Debugf(format, args...) | |||
| } | |||
| // Panicf log panic with format | |||
| func Panicf(Operation string, format string, args ...interface{}) { | |||
| _logger.WithFields(logrus.Fields{ | |||
| "Operation": Operation, | |||
| @@ -21,10 +21,12 @@ import ( | |||
| "github.com/gin-gonic/gin" | |||
| ) | |||
| // Server server interface | |||
| type Server interface { | |||
| ListenAndServe() error | |||
| } | |||
| // InitServer init server | |||
| func InitServer(address string, r *gin.Engine) Server { | |||
| server := endless.NewServer(address, r) | |||