├── .drone.yml
├── .gitignore
├── .gitmodules
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── app
├── code
│ ├── api.go
│ └── error.go
├── enums
│ └── Menu.go
├── http
│ ├── binding
│ │ ├── Menu.go
│ │ ├── Role.go
│ │ ├── User.go
│ │ ├── Users.go
│ │ └── common.go
│ ├── controller
│ │ ├── api
│ │ │ ├── Auth.go
│ │ │ ├── Menu.go
│ │ │ ├── Role.go
│ │ │ ├── TestController.go
│ │ │ └── Users.go
│ │ └── web
│ │ │ └── TestController.go
│ ├── repo
│ │ ├── Auth.go
│ │ ├── Menu.go
│ │ ├── Role.go
│ │ └── Users.go
│ └── resource
│ │ ├── Common.go
│ │ └── User.go
└── server
│ ├── apiServer.go
│ ├── gin.go
│ └── webServer.go
├── asset
├── embed.go
├── public
│ ├── robots.txt
│ └── static
│ │ ├── css
│ │ └── main.css
│ │ ├── img
│ │ └── API.png
│ │ └── js
│ │ └── app.js
└── views
│ ├── index.html
│ └── layout
│ ├── footer.html
│ └── head.html
├── boot
└── db
│ └── database.go
├── cmd
├── api
│ └── main.go
├── migrator
│ └── main.go
├── test
│ └── main.go
└── web
│ └── main.go
├── config
├── app.go
├── casbin.go
├── cloudflare.go
├── db.go
├── dbPool.go
├── hash.go
├── httpclient.go
├── jwt.go
└── log.go
├── go.mod
├── go.sum
├── model
├── Menu.go
├── Role.go
├── User.go
├── common.go
└── scope
│ └── common.go
└── router
├── api.go
└── web.go
/.drone.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | name: goAdmin-后端
3 | type: docker
4 |
5 | clone:
6 | depth: 1
7 |
8 | steps:
9 | - name: submodules
10 | image: alpine/git
11 | environment:
12 | SSH_PRIVATE_KEY:
13 | from_secret: ssh_key
14 | commands:
15 | - sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
16 | - 'which ssh-agent || ( apk update && apk --no-cache add openssh-client)'
17 | - eval $(ssh-agent -s)
18 | - echo "$SSH_PRIVATE_KEY" > deploy.key
19 | - chmod 0600 deploy.key
20 | - ssh-add deploy.key
21 | - mkdir -p ~/.ssh
22 | - echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
23 | - git submodule update --init --recursive
24 |
25 | - name: 构建服务端镜像
26 | pull: if-not-exists
27 | image: plugins/docker
28 | settings:
29 | storage_driver: vfs
30 | tags:
31 | - latest
32 | - ${DRONE_BUILD_NUMBER}
33 | insecure: true
34 | use_cache: true
35 | registry:
36 | from_secret: harbor_address
37 | repo:
38 | from_secret: harbor_repo
39 | username:
40 | from_secret: harbor_user
41 | password:
42 | from_secret: harbor_pass
43 | context: ./
44 | dockerfile: ./Dockerfile
45 | when:
46 | status:
47 | - success
48 |
49 |
50 | - name: deploy
51 | image: alpine
52 | pull: if-not-exists
53 | commands:
54 | - sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
55 | - apk update
56 | - apk --no-cache add curl
57 | # portainer hook
58 | # - curl -XPOST http://192.168.1.9:9000/api/stacks/webhooks/ea389514-d123-4ce2-88e1-7385029e8af0
59 |
60 | - name: send telegram notification
61 | image: appleboy/drone-telegram
62 | pull: if-not-exists
63 | when:
64 | status:
65 | - success
66 | - failure
67 | settings:
68 | token:
69 | from_secret: telegram_token
70 | to:
71 | from_secret: telegram_to
72 | format: markdown
73 | message: >
74 | {{#success build.status}}
75 | ✅ Build #{{build.number}} of `{{repo.name}}` succeeded.
76 | 📝 Commit by {{commit.author}} on `{{commit.branch}}`:
77 | ```
78 | {{commit.message}}
79 | ```
80 | 🌐 {{ build.link }}
81 | {{else}}
82 | ❌ Build #{{build.number}} of `{{repo.name}}` failed.
83 | 📝 Commit by {{commit.author}} on `{{commit.branch}}`:
84 | ```
85 | {{commit.message}}
86 | ```
87 | 🌐 {{ build.link }}
88 | {{/success}}
89 |
90 | trigger:
91 | branch:
92 | - master
93 | event:
94 | - push
95 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .history/
2 | .env
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib"]
2 | path = lib
3 | url = git@github.com:anerg2046/go-app-lib.git
4 | [submodule "app/http/middleware"]
5 | path = app/http/middleware
6 | url = git@github.com:anerg2046/go-app-middleware.git
7 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine as builder
2 |
3 | ENV CGO_ENABLED 0
4 | ENV GOPROXY=https://goproxy.cn,direct
5 |
6 | WORKDIR /build
7 |
8 | ADD go.mod .
9 | ADD go.sum .
10 | RUN go mod download
11 |
12 | COPY . .
13 | # RUN cd ./cmd/cron && go build -ldflags="-s -w" -o /app/cron.job
14 | RUN cd ./cmd/api && go build -o /app/server.app
15 |
16 |
17 | FROM alpine:latest
18 |
19 | ENV TZ Asia/Shanghai
20 |
21 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories \
22 | && apk update --no-cache \
23 | && apk add --no-cache ca-certificates tzdata \
24 | && cp /usr/share/zoneinfo/${TZ} /etc/localtime \
25 | && rm -rf /tmp/*
26 |
27 | WORKDIR /app
28 | COPY --from=builder /app/server.app /app/server.app
29 |
30 | CMD ["./server.app"]
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-present, pure-admin
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.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | db:
2 | go run cmd/migrator/main.go
3 |
4 | api:
5 | go run cmd/api/main.go
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GO-ADMIN-SERVER
2 |
3 | [](LICENSE)
4 |
5 | ## 简介
6 |
7 | 本项目是基于 [go-app-template](https://github.com/anerg2046/go-app-template) 开发的通用后台基础代码
8 |
9 | 配套的前端代码为 [go-admin-front](https://github.com/anerg2046/go-admin-front)
10 |
11 | ### 已完成的部分:
12 |
13 | - 完善的菜单管理
14 | - 完善的角色管理
15 | - 完善的用户管理
16 |
17 | ### 特性
18 |
19 | - 基于casbin对菜单的显示,和后端请求接口均可按角色分配权限
20 |
21 | ### 说明
22 |
23 | 采用了git子模块的方式,所以clone的时候请用下面的命令
24 |
25 | `git clone --recurse-submodules git@github.com:anerg2046/go-admin-server.git`
26 |
27 | 需要配置的东西全部在 `config` 目录中,还请自行查看
28 |
29 | ### 演示地址
30 |
31 | https://admin.fabraze.com/
32 |
33 | 超级管理员:
34 |
35 | admin admin1234
36 |
37 | 普通用户:
38 |
39 | test test123456
40 |
41 | ### 其它
42 |
43 | 克隆代码以后,运行`go mod tidy`安装依赖
44 |
45 | 在根目录有两个快捷命令
46 |
47 | - `make db` 创建数据库,和默认数据,前提是已经配置好数据库
48 | - `make api` 执行服务端接口程序
--------------------------------------------------------------------------------
/app/code/api.go:
--------------------------------------------------------------------------------
1 | package code
2 |
3 | var (
4 | ErrServer = NewError(10001, "服务异常") //服务异常
5 | ErrParam = NewError(10002, "参数有误") //参数有误
6 | ErrToken = NewError(10003, "Token无效") //Token无效
7 | ErrEmptyToken = NewError(10004, "请求未携带Token") //请求未携带Token
8 | ErrRoute = NewError(10005, "请求地址错误") //请求地址错误
9 | ErrRecordNotFound = NewError(20001, "未查询到记录") //未查询到记录
10 |
11 | ErrBusinessTakeLimit = NewError(40001, "商机领取到达上限") //商机领取到达上限
12 | ErrBusinessTaked = NewError(40002, "商机已被他人领取") //商机已被他人领取
13 | )
14 |
--------------------------------------------------------------------------------
/app/code/error.go:
--------------------------------------------------------------------------------
1 | package code
2 |
3 | type Error interface {
4 | // i 为了避免被其他包实现
5 | i()
6 | ErrCode() int
7 | ErrMsg() string
8 | }
9 |
10 | type err struct {
11 | Code int // 业务编码
12 | Msg string // 错误描述
13 | }
14 |
15 | func NewError(code int, msg string) Error {
16 | return &err{
17 | Code: code,
18 | Msg: msg,
19 | }
20 | }
21 |
22 | func (e *err) i() {}
23 |
24 | func (e *err) ErrCode() int {
25 | return e.Code
26 | }
27 | func (e *err) ErrMsg() string {
28 | return e.Msg
29 | }
30 |
--------------------------------------------------------------------------------
/app/enums/Menu.go:
--------------------------------------------------------------------------------
1 | package enums
2 |
3 | type MenuGenre uint16 // 菜单类型
4 |
5 | const (
6 | _ MenuGenre = iota
7 | Menu_MENU //菜单
8 | Menu_ACTION //操作
9 | Menu_LINK //外链
10 | )
11 |
--------------------------------------------------------------------------------
/app/http/binding/Menu.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "go-app/model"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | var Menu = new(menuBinding)
10 |
11 | type menuBinding struct{}
12 |
13 | func (u menuBinding) Edit() gin.HandlerFunc {
14 | return func(c *gin.Context) {
15 | var data model.Menu
16 | params, err := bindParams(c, data)
17 | if err != nil {
18 | c.Abort()
19 | return
20 | }
21 | c.Set("MenuEditParams", params)
22 | c.Next()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/http/binding/Role.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "go-app/model"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | var Role = new(roleBinding)
10 |
11 | type roleBinding struct{}
12 |
13 | func (roleBinding) Edit() gin.HandlerFunc {
14 | return func(c *gin.Context) {
15 | var data model.Role
16 | params, err := bindParams(c, data)
17 | if err != nil {
18 | c.Abort()
19 | return
20 | }
21 | c.Set("RoleEditParams", params)
22 | c.Next()
23 | }
24 | }
25 |
26 | type RoleAssign struct {
27 | ID uint `form:"id" json:"id" binding:"required" label:"角色ID"`
28 | MenuIDs []uint `json:"menu_ids,omitempty"`
29 | }
30 |
31 | func (roleBinding) Assign() gin.HandlerFunc {
32 | return func(c *gin.Context) {
33 | var data RoleAssign
34 | params, err := bindParams(c, data)
35 | if err != nil {
36 | c.Abort()
37 | return
38 | }
39 | c.Set("RoleAssignParams", params)
40 | c.Next()
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/http/binding/User.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "go-app/app/code"
5 | "go-app/config"
6 | "go-app/lib/httpclient"
7 | "go-app/lib/response"
8 | "net/http"
9 |
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | var User = new(userBinding)
14 |
15 | type userBinding struct{}
16 |
17 | type UserLoginParams struct {
18 | Username string `form:"username" binding:"required" label:"帐号"`
19 | Password string `form:"password" binding:"required" label:"密码"`
20 | TurnstileToken string `form:"turnstileToken" binding:"required" label:"TurnstileToken"`
21 | }
22 |
23 | func (u userBinding) Login() gin.HandlerFunc {
24 | return func(c *gin.Context) {
25 | var data UserLoginParams
26 | params, err := bindParams(c, data)
27 | if err != nil {
28 | c.Abort()
29 | return
30 | }
31 | c.Set("UserLoginParams", params)
32 | c.Set("TurnstileToken", params.TurnstileToken)
33 | c.Next()
34 | }
35 | }
36 |
37 | func (u userBinding) VerifyTurnstile() gin.HandlerFunc {
38 | return func(c *gin.Context) {
39 | payload := map[string]string{
40 | "secret": config.CLOUDFLARE.TurnstileSecret,
41 | "response": c.MustGet("TurnstileToken").(string),
42 | }
43 | url := config.CLOUDFLARE.TurnstileUrl
44 | var result = struct {
45 | Success bool `json:"success,omitempty"`
46 | }{}
47 | httpclient.C().R().SetResult(&result).SetFormData(payload).Post(url)
48 | if !result.Success {
49 | c.JSON(http.StatusUnauthorized, response.NewJson().Error(code.NewError(-1, "未通过人机验证")))
50 | c.Abort()
51 | return
52 | }
53 | c.Next()
54 | }
55 | }
56 |
57 | type RefreshTokenParams struct {
58 | RefreshToken string `form:"refreshToken" binding:"required" label:"Token"`
59 | }
60 |
61 | func (userBinding) RefreshToken() gin.HandlerFunc {
62 | return func(c *gin.Context) {
63 | var data RefreshTokenParams
64 | params, err := bindParams(c, data)
65 | if err != nil {
66 | c.Abort()
67 | return
68 | }
69 | c.Set("RefreshTokenParams", params)
70 | c.Next()
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/http/binding/Users.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | import (
4 | "go-app/model"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | var Users = new(usersBinding)
10 |
11 | type usersBinding struct{}
12 |
13 | func (usersBinding) Edit() gin.HandlerFunc {
14 | return func(c *gin.Context) {
15 | var data model.User
16 | params, err := bindParams(c, data)
17 | if err != nil {
18 | c.Abort()
19 | return
20 | }
21 | c.Set("UsersEditParams", params)
22 | c.Next()
23 | }
24 | }
25 |
26 | type UserAssign struct {
27 | ID uint `form:"id" json:"id" binding:"required" label:"用户ID"`
28 | Roles []string `form:"roles" json:"roles,omitempty"`
29 | }
30 |
31 | func (usersBinding) Assign() gin.HandlerFunc {
32 | return func(c *gin.Context) {
33 | var data UserAssign
34 | params, err := bindParams(c, data)
35 | if err != nil {
36 | c.Abort()
37 | return
38 | }
39 | c.Set("UserAssignParams", params)
40 | c.Next()
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/http/binding/common.go:
--------------------------------------------------------------------------------
1 | package binding
2 |
3 | // 一些通用绑定及验证
4 |
5 | import (
6 | "go-app/app/code"
7 | "go-app/config"
8 | "go-app/lib/response"
9 | "go-app/lib/validator"
10 | "net/http"
11 |
12 | "github.com/gin-gonic/gin"
13 | "github.com/gin-gonic/gin/binding"
14 | )
15 |
16 | var Common = new(common)
17 |
18 | type common struct{}
19 |
20 | type ID struct {
21 | ID uint `form:"id" json:"id" binding:"required,gte=1" label:"ID"`
22 | }
23 |
24 | type Genre struct {
25 | Genre uint16 `form:"genre" json:"genre" binding:"required,gte=1" label:"类型"`
26 | }
27 |
28 | type Pager struct {
29 | Page int `form:"page" json:"page,omitempty" binding:"omitempty,gte=1" label:"页码"`
30 | Size int `form:"size" json:"size,omitempty" binding:"omitempty,gte=1" label:"每页数量"`
31 | }
32 |
33 | type Keyword struct {
34 | Keyword string `form:"keyword" json:"keyword"`
35 | }
36 |
37 | func (common) ID() gin.HandlerFunc {
38 | return func(c *gin.Context) {
39 | var err error
40 | var id ID
41 | params, err := bindParams(c, id)
42 | if err != nil {
43 | c.Abort()
44 | return
45 | }
46 | c.Set("IdParams", params)
47 | c.Next()
48 | }
49 | }
50 |
51 | func (common) Genre() gin.HandlerFunc {
52 | return func(c *gin.Context) {
53 | var genre Genre
54 | params, err := bindParams(c, genre)
55 | if err != nil {
56 | c.Abort()
57 | return
58 | }
59 | c.Set("GenreParams", params.Genre)
60 | c.Next()
61 | }
62 | }
63 |
64 | func (common) Pager() gin.HandlerFunc {
65 | return func(c *gin.Context) {
66 | var pager Pager
67 | params, err := bindParams(c, pager)
68 | if err != nil {
69 | c.Abort()
70 | return
71 | }
72 | if params.Page == 0 {
73 | params.Page = 1
74 | }
75 | if params.Size == 0 {
76 | params.Size = 50
77 | }
78 | c.Set("PagerParams", params)
79 | c.Next()
80 | }
81 | }
82 |
83 | func (common) Keyword() gin.HandlerFunc {
84 | return func(c *gin.Context) {
85 | var data Keyword
86 | params, err := bindParams(c, data)
87 | if err != nil {
88 | c.Abort()
89 | return
90 | }
91 | c.Set("KeywordParams", params)
92 | c.Next()
93 | }
94 | }
95 |
96 | func bindParams[T any](c *gin.Context, params T) (T, error) {
97 | var err error
98 | if c.ContentType() == "application/json" {
99 | err = c.ShouldBindBodyWith(¶ms, binding.JSON)
100 | } else {
101 | err = c.ShouldBind(¶ms)
102 | }
103 | if err != nil {
104 | if config.APP.Mode == config.MODE_API {
105 | c.JSON(http.StatusOK, response.NewJson().Error(code.ErrParam).WithData(validator.Error(err)))
106 | } else if config.APP.Mode == config.MODE_WEB {
107 | c.Redirect(http.StatusTemporaryRedirect, "/404")
108 | } else {
109 | c.AbortWithStatus(http.StatusInternalServerError)
110 | }
111 | }
112 | return params, err
113 | }
114 |
--------------------------------------------------------------------------------
/app/http/controller/api/Auth.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "go-app/app/http/binding"
5 | "go-app/app/http/repo"
6 | "go-app/lib/response"
7 |
8 | "github.com/gin-gonic/gin"
9 | )
10 |
11 | var AuthController = new(authController)
12 |
13 | type authController struct{}
14 |
15 | func (authController) Login(c *gin.Context) response.Json {
16 | params := c.MustGet("UserLoginParams").(binding.UserLoginParams)
17 | return response.JsonResponse(repo.Auth.Login(params))
18 | }
19 |
20 | func (authController) RefreshToken(c *gin.Context) response.Json {
21 | params := c.MustGet("RefreshTokenParams").(binding.RefreshTokenParams)
22 | return response.JsonResponse(repo.Auth.RefreshToken(params))
23 | }
24 |
--------------------------------------------------------------------------------
/app/http/controller/api/Menu.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "go-app/app/http/binding"
5 | "go-app/app/http/repo"
6 | "go-app/config"
7 | "go-app/lib/response"
8 | "go-app/model"
9 |
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | var MenuController = new(menuController)
14 |
15 | type menuController struct{}
16 |
17 | func (menuController) Router(c *gin.Context) response.Json {
18 | auth := c.MustGet("JwtAuth").(*config.JwtClaims)
19 | return response.JsonResponse(repo.Menu.Router(auth))
20 | }
21 |
22 | /** 管理相关接口 */
23 |
24 | // 所有菜单
25 | func (menuController) List(c *gin.Context) response.Json {
26 | return response.JsonResponse(repo.Menu.List())
27 | }
28 |
29 | // 添加/编辑菜单
30 | func (menuController) Edit(c *gin.Context) response.Json {
31 | params := c.MustGet("MenuEditParams").(model.Menu)
32 | return response.JsonResponse(repo.Menu.Edit(params))
33 | }
34 |
35 | // 删除菜单
36 | func (menuController) Del(c *gin.Context) response.Json {
37 | params := c.MustGet("IdParams").(binding.ID)
38 | return response.JsonResponse(repo.Menu.Del(params.ID))
39 | }
40 |
--------------------------------------------------------------------------------
/app/http/controller/api/Role.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "go-app/app/http/binding"
5 | "go-app/app/http/repo"
6 | "go-app/lib/response"
7 | "go-app/model"
8 |
9 | "github.com/gin-gonic/gin"
10 | )
11 |
12 | var RoleController = new(roleController)
13 |
14 | type roleController struct{}
15 |
16 | /** 管理相关接口 */
17 |
18 | // 所有角色
19 | func (roleController) List(c *gin.Context) response.Json {
20 | return response.JsonResponse(repo.Role.List())
21 | }
22 |
23 | // 添加/编辑角色
24 | func (roleController) Edit(c *gin.Context) response.Json {
25 | params := c.MustGet("RoleEditParams").(model.Role)
26 | return response.JsonResponse(repo.Role.Edit(params))
27 | }
28 |
29 | // 删除角色
30 | func (roleController) Del(c *gin.Context) response.Json {
31 | params := c.MustGet("IdParams").(binding.ID)
32 | return response.JsonResponse(repo.Role.Del(params.ID))
33 | }
34 |
35 | // 角色权限
36 | func (roleController) Permission(c *gin.Context) response.Json {
37 | params := c.MustGet("IdParams").(binding.ID)
38 | return response.JsonResponse(repo.Role.Permission(params.ID))
39 | }
40 |
41 | // 角色指派权限
42 | func (roleController) Assign(c *gin.Context) response.Json {
43 | params := c.MustGet("RoleAssignParams").(binding.RoleAssign)
44 | return response.JsonResponse(repo.Role.Assign(params))
45 | }
46 |
--------------------------------------------------------------------------------
/app/http/controller/api/TestController.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "go-app/lib/response"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | var TestController = new(testController)
10 |
11 | type testController struct{}
12 |
13 | func (testController) Ip(c *gin.Context) response.Json {
14 | return response.NewJson().WithData(c.ClientIP())
15 | }
16 |
--------------------------------------------------------------------------------
/app/http/controller/api/Users.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "go-app/app/http/binding"
5 | "go-app/app/http/repo"
6 | "go-app/lib/response"
7 | "go-app/model"
8 |
9 | "github.com/gin-gonic/gin"
10 | )
11 |
12 | var UsersController = new(usersController)
13 |
14 | type usersController struct{}
15 |
16 | /** 管理相关接口 */
17 |
18 | // 所有用户
19 | func (usersController) List(c *gin.Context) response.Json {
20 | keyword := c.MustGet("KeywordParams").(binding.Keyword)
21 | pager := c.MustGet("PagerParams").(binding.Pager)
22 | return response.JsonResponse(repo.Users.List(keyword, pager))
23 | }
24 |
25 | // 添加/编辑用户
26 | func (usersController) Edit(c *gin.Context) response.Json {
27 | params := c.MustGet("UsersEditParams").(model.User)
28 | return response.JsonResponse(repo.Users.Edit(params))
29 | }
30 |
31 | // 删除用户
32 | func (usersController) Del(c *gin.Context) response.Json {
33 | params := c.MustGet("IdParams").(binding.ID)
34 | return response.JsonResponse(repo.Users.Del(params.ID))
35 | }
36 |
37 | // 用户分配角色
38 | func (usersController) Assign(c *gin.Context) response.Json {
39 | params := c.MustGet("UserAssignParams").(binding.UserAssign)
40 | return response.JsonResponse(repo.Users.Assign(params))
41 | }
42 |
--------------------------------------------------------------------------------
/app/http/controller/web/TestController.go:
--------------------------------------------------------------------------------
1 | package web
2 |
3 | import (
4 | "go-app/lib/response"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | type TestController struct{}
10 |
11 | func (TestController) Ip(c *gin.Context) response.Html {
12 | return response.HtmlResponse("index.html").WithData("ip", c.ClientIP())
13 | }
14 |
--------------------------------------------------------------------------------
/app/http/repo/Auth.go:
--------------------------------------------------------------------------------
1 | package repo
2 |
3 | import (
4 | "errors"
5 | "go-app/app/code"
6 | "go-app/app/http/binding"
7 | "go-app/app/http/resource"
8 | "go-app/boot/db"
9 | "go-app/config"
10 | "go-app/model"
11 | "time"
12 |
13 | jwtlib "go-app/lib/jwt"
14 | "go-app/lib/str"
15 |
16 | "github.com/golang-jwt/jwt"
17 | "github.com/wumansgy/goEncrypt/hash"
18 | "gorm.io/gorm"
19 | )
20 |
21 | var Auth = new(authRepo)
22 |
23 | type authRepo struct{}
24 |
25 | func (ar *authRepo) Login(params binding.UserLoginParams) (result resource.Login, e code.Error) {
26 | var user model.User
27 | err := db.Conn.Where("LOWER(username) = LOWER(?) OR phone = ?", params.Username, params.Username).First(&user).Error
28 | if err != nil {
29 | if errors.Is(err, gorm.ErrRecordNotFound) {
30 | e = code.NewError(-1, "用户不存在")
31 | return
32 | }
33 | e = code.ErrServer
34 | return
35 | }
36 |
37 | if user.Password != hash.HmacSha256Hex([]byte(config.HASH.HmacSha256Key), params.Password) {
38 | e = code.NewError(-1, "密码不正确")
39 | return
40 | }
41 |
42 | result.Username = user.Username
43 | result.AccessToken, result.Expires = ar.genJwtToken(user)
44 | result.RefreshToken = ar.genRefreshToken(user)
45 | result.Roles = user.Roles
46 | return
47 | }
48 |
49 | func (ar *authRepo) RefreshToken(params binding.RefreshTokenParams) (result resource.Token, e code.Error) {
50 | var userToken model.UserToken
51 | err := db.Conn.Preload("User").Where(&model.UserToken{Token: params.RefreshToken}).First(&userToken).Error
52 | if err != nil {
53 | if errors.Is(err, gorm.ErrRecordNotFound) {
54 | e = code.ErrToken
55 | return
56 | }
57 | e = code.ErrServer
58 | return
59 | }
60 |
61 | if userToken.ExpiredAt.Before(time.Now()) {
62 | e = code.ErrToken
63 | return
64 | }
65 |
66 | result.AccessToken, result.Expires = ar.genJwtToken(*userToken.User)
67 | result.RefreshToken = userToken.Token
68 | return
69 | }
70 |
71 | // 生成jwt token即AccessToken
72 | func (authRepo) genJwtToken(user model.User) (string, time.Time) {
73 | exp := time.Now().Add(time.Minute * 10)
74 | claims := config.JwtClaims{
75 | ID: user.ID,
76 | Username: user.Username,
77 | StandardClaims: jwt.StandardClaims{
78 | ExpiresAt: exp.Unix(),
79 | },
80 | }
81 |
82 | token, err := jwtlib.CreateToken(claims)
83 | if err != nil {
84 | panic(err)
85 | }
86 | return token, exp
87 | }
88 |
89 | // 生成随机数token即RefreshToken
90 | func (authRepo) genRefreshToken(user model.User) string {
91 | var userToken model.UserToken
92 | db.Conn.Where(&model.UserToken{ID: user.ID}).FirstOrInit(&userToken)
93 | userToken.Token = str.Random(128)
94 | userToken.ExpiredAt = time.Now().AddDate(0, 0, 30)
95 | db.Conn.Save(&userToken)
96 |
97 | return userToken.Token
98 | }
99 |
--------------------------------------------------------------------------------
/app/http/repo/Menu.go:
--------------------------------------------------------------------------------
1 | package repo
2 |
3 | import (
4 | "errors"
5 | "go-app/app/code"
6 | "go-app/app/enums"
7 | "go-app/boot/db"
8 | "go-app/config"
9 | "go-app/lib/rbac"
10 | "go-app/lib/util"
11 | "go-app/model"
12 | "go-app/model/scope"
13 |
14 | "gorm.io/gorm"
15 | )
16 |
17 | var Menu = new(menuRepo)
18 |
19 | type menuRepo struct{}
20 |
21 | // 用户可显示的菜单
22 | func (menuRepo) Router(auth *config.JwtClaims) (result []model.Menu, e code.Error) {
23 | var allowMenus []model.Menu
24 | var menus []model.Menu
25 | db.Conn.Model(&model.Menu{}).Where("genre IN ?", []enums.MenuGenre{enums.Menu_MENU, enums.Menu_LINK}).Order("queue ASC").Find(&menus)
26 | for _, menu := range menus {
27 | if ok, _ := rbac.New().Enforce(auth.Username, menu.Name, "menu"); ok {
28 | allowMenus = append(allowMenus, menu)
29 | }
30 | }
31 | result = MenuListToTree(allowMenus, model.Menu{})
32 | return
33 | }
34 |
35 | // 菜单列表转树状结构
36 | func MenuListToTree(rows []model.Menu, parent model.Menu) []model.Menu {
37 | tree := make([]model.Menu, 0)
38 | for _, row := range rows {
39 | if row.ParentID == parent.ID {
40 | row.Children = MenuListToTree(rows, row)
41 | tree = append(tree, row)
42 | }
43 | }
44 | return tree
45 | }
46 |
47 | // 所有菜单列表
48 | func (menuRepo) List() (result []model.Menu, e code.Error) {
49 | var menus []model.Menu
50 | db.Conn.Model(&model.Menu{}).Order("queue ASC").Find(&menus)
51 | result = MenuListToTree(menus, model.Menu{})
52 | return
53 | }
54 |
55 | func (m menuRepo) Edit(params model.Menu) (any, e code.Error) {
56 | if params.ID > 0 {
57 | var menu model.Menu
58 | err := db.Conn.Preload("Children", scope.ExpandChildren).Where("id = ?", params.ID).First(&menu).Error
59 | if err != nil {
60 | if errors.Is(err, gorm.ErrRecordNotFound) {
61 | e = code.NewError(-1, "没有这个菜单")
62 | return
63 | }
64 | e = code.ErrServer
65 | return
66 | }
67 | childIDs := m.getAllChildrenID(menu)
68 | if util.Contain(params.ParentID, childIDs) || params.ParentID == params.ID {
69 | e = code.NewError(-1, "层级设置错误")
70 | return
71 | }
72 | }
73 | if params.Genre == enums.Menu_ACTION {
74 | params.Component = ""
75 | params.Meta.ShowLink = false
76 | params.Meta.Icon = ""
77 | } else if params.Genre == enums.Menu_LINK {
78 | params.Component = ""
79 | params.Meta.Icon = ""
80 | }
81 | if err := db.Conn.Select("*").Save(¶ms).Error; err != nil {
82 | e = code.NewError(-1, err.Error())
83 | }
84 | return
85 | }
86 |
87 | func (m menuRepo) Del(id uint) (any, e code.Error) {
88 | var menu model.Menu
89 | if db.Conn.Preload("Children").First(&menu, id).Error != nil {
90 | e = code.ErrServer
91 | return
92 | }
93 | if len(menu.Children) > 0 {
94 | e = code.NewError(-1, "请先删除菜单的下级")
95 | return
96 | }
97 | if err := db.Conn.Delete(&menu).Error; err != nil {
98 | e = code.NewError(-1, err.Error())
99 | }
100 | return
101 | }
102 |
103 | func (m menuRepo) getAllChildrenID(menu model.Menu) (ids []uint) {
104 | for _, child := range menu.Children {
105 | ids = append(ids, child.ID)
106 | ids = append(ids, m.getAllChildrenID(child)...)
107 | }
108 | return ids
109 | }
110 |
--------------------------------------------------------------------------------
/app/http/repo/Role.go:
--------------------------------------------------------------------------------
1 | package repo
2 |
3 | import (
4 | "go-app/app/code"
5 | "go-app/app/enums"
6 | "go-app/app/http/binding"
7 | "go-app/boot/db"
8 | "go-app/lib/logger"
9 | "go-app/lib/rbac"
10 | "go-app/model"
11 |
12 | "go.uber.org/zap"
13 | )
14 |
15 | var Role = new(roleRepo)
16 |
17 | type roleRepo struct{}
18 |
19 | func (roleRepo) List() (result []model.Role, e code.Error) {
20 | db.Conn.Order("queue ASC").Find(&result)
21 | return
22 | }
23 |
24 | func (roleRepo) Edit(params model.Role) (any, e code.Error) {
25 | var count int64
26 | if params.ID > 0 {
27 | db.Conn.Model(&model.Role{}).Where("id <> ? AND code = ?", params.ID, params.Code).Count(&count)
28 | if count > 0 {
29 | e = code.NewError(-1, "角色编码不能重复")
30 | return
31 | }
32 | } else {
33 | db.Conn.Model(&model.Role{}).Where("code = ?", params.Code).Count(&count)
34 | if count > 0 {
35 | e = code.NewError(-1, "角色编码不能重复")
36 | return
37 | }
38 | }
39 | err := db.Conn.Save(¶ms).Error
40 | if err != nil {
41 | e = code.ErrServer
42 | }
43 | return
44 | }
45 |
46 | func (roleRepo) Del(id uint) (any, e code.Error) {
47 | var role model.Role
48 | if db.Conn.First(&role, id).Error != nil {
49 | e = code.ErrServer
50 | return
51 | }
52 | if err := db.Conn.Delete(&role).Error; err != nil {
53 | e = code.NewError(-1, err.Error())
54 | }
55 | return
56 | }
57 |
58 | // 获取角色的权限
59 | func (roleRepo) Permission(id uint) (menuIDs []uint, e code.Error) {
60 | var role model.Role
61 | db.Conn.Where("id = ?", id).First(&role)
62 |
63 | var menuNames []string
64 | permissions := rbac.New().GetPermissionsForUser(role.Code)
65 | for _, p := range permissions {
66 | menuNames = append(menuNames, p[1])
67 | }
68 |
69 | // 必须加上parent_id排序,否则前端显示可能有bug
70 | // 因为在角色指派权限的时候需要设置已有的,而如果先设置子叶,再设置父叶,会出现半选变成全选的问题
71 | db.Conn.Model(&model.Menu{}).Where("name IN ? OR path IN ?", menuNames, menuNames).Order("parent_id ASC").Pluck("id", &menuIDs)
72 |
73 | return
74 | }
75 |
76 | // 角色指派权限
77 | func (roleRepo) Assign(params binding.RoleAssign) (any, e code.Error) {
78 | var role model.Role
79 | db.Conn.Where("id = ?", params.ID).First(&role)
80 |
81 | // 先要删除该角色 所有权限
82 | rbac.New().DeletePermissionsForUser(role.Code)
83 |
84 | var menus []model.Menu
85 | db.Conn.Model(&model.Menu{}).Where("id IN ?", params.MenuIDs).Find(&menus)
86 | for _, menu := range menus {
87 | if menu.Genre == enums.Menu_ACTION {
88 | //针对服务端的接口权限
89 | _, err := rbac.New().AddPermissionForUser(role.Code, menu.Path, "request")
90 | if err != nil {
91 | logger.Error("[Casbin]", zap.Error(err))
92 | e = code.ErrServer
93 | return
94 | }
95 | } else { //菜单显示权限
96 | _, err := rbac.New().AddPermissionForUser(role.Code, menu.Name, "menu")
97 | if err != nil {
98 | logger.Error("[Casbin]", zap.Error(err))
99 | e = code.ErrServer
100 | return
101 | }
102 | }
103 | }
104 | return
105 | }
106 |
--------------------------------------------------------------------------------
/app/http/repo/Users.go:
--------------------------------------------------------------------------------
1 | package repo
2 |
3 | import (
4 | "errors"
5 | "go-app/app/code"
6 | "go-app/app/http/binding"
7 | "go-app/app/http/resource"
8 | "go-app/boot/db"
9 | "go-app/config"
10 | "go-app/lib/logger"
11 | "go-app/lib/rbac"
12 | "go-app/model"
13 | "go-app/model/scope"
14 |
15 | "github.com/wumansgy/goEncrypt/hash"
16 | "go.uber.org/zap"
17 | "gorm.io/gorm"
18 | )
19 |
20 | var Users = new(usersRepo)
21 |
22 | type usersRepo struct{}
23 |
24 | func (usersRepo) List(keyword binding.Keyword, pager binding.Pager) (data resource.ApiList, e code.Error) {
25 | var users []model.UserApi
26 | query := db.Conn.Model(&model.User{})
27 | if keyword.Keyword != "" {
28 | query.Where("username = ? OR phone = ?", keyword.Keyword, keyword.Keyword)
29 | }
30 | query.Session(&gorm.Session{})
31 |
32 | // 查询总数用于分页
33 | var count int64
34 | query.Count(&count)
35 | data.Total = count
36 |
37 | query.Scopes(scope.Paginate(pager)).Order("id DESC").Find(&users)
38 | data.List = users
39 | return
40 | }
41 |
42 | func (usersRepo) Edit(params model.User) (any, e code.Error) {
43 | var count int64
44 | if params.ID > 0 {
45 | db.Conn.Model(&model.User{}).Where("id <> ? AND username = ?", params.ID, params.Username).Count(&count)
46 | if count > 0 {
47 | e = code.NewError(-1, "用户名不能重复")
48 | return
49 | }
50 | db.Conn.Model(&model.User{}).Where("id <> ? AND phone = ?", params.ID, params.Phone).Count(&count)
51 | if count > 0 {
52 | e = code.NewError(-1, "手机号不能重复")
53 | return
54 | }
55 | } else {
56 | db.Conn.Model(&model.User{}).Where("username = ?", params.Username).Count(&count)
57 | if count > 0 {
58 | e = code.NewError(-1, "用户名不能重复")
59 | return
60 | }
61 | db.Conn.Model(&model.User{}).Where("phone = ?", params.Phone).Count(&count)
62 | if count > 0 {
63 | e = code.NewError(-1, "手机号不能重复")
64 | return
65 | }
66 | }
67 |
68 | if params.Password != "" {
69 | params.Password = hash.HmacSha256Hex([]byte(config.HASH.HmacSha256Key), params.Password)
70 | err := db.Conn.Save(¶ms).Error
71 | if err != nil {
72 | e = code.NewError(-1, err.Error())
73 | return
74 | }
75 | } else {
76 | err := db.Conn.Omit("password").Save(¶ms).Error
77 | if err != nil {
78 | e = code.NewError(-1, err.Error())
79 | return
80 | }
81 | }
82 |
83 | return
84 | }
85 |
86 | func (usersRepo) Del(id uint) (any, e code.Error) {
87 | var user model.User
88 | if db.Conn.First(&user, id).Error != nil {
89 | e = code.ErrServer
90 | return
91 | }
92 | if err := db.Conn.Delete(&user).Error; err != nil {
93 | e = code.NewError(-1, err.Error())
94 | }
95 | return
96 | }
97 |
98 | func (usersRepo) Assign(params binding.UserAssign) (any, e code.Error) {
99 | var user model.User
100 | if err := db.Conn.First(&user, params.ID).Error; err != nil {
101 | if errors.Is(err, gorm.ErrRecordNotFound) {
102 | e = code.NewError(-1, "用户不存在")
103 | return
104 | }
105 | e = code.ErrServer
106 | return
107 | }
108 |
109 | if user.Username == "测试用户" || user.Username == "废墟" {
110 | e = code.NewError(-1, "默认用户不能分配角色")
111 | return
112 | }
113 |
114 | _, err := rbac.New().DeleteRolesForUser(user.Username)
115 | if err != nil {
116 | logger.Error("[CASBIN]", zap.NamedError("删除用户角色出错", err))
117 | e = code.ErrServer
118 | return
119 | }
120 |
121 | if len(params.Roles) > 0 {
122 | var roleCodes []string
123 | db.Conn.Model(&model.Role{}).Where("code IN ?", params.Roles).Pluck("code", &roleCodes)
124 | _, err := rbac.New().AddRolesForUser(user.Username, roleCodes)
125 | if err != nil {
126 | logger.Error("[CASBIN]", zap.NamedError("批量添加用户角色出错", err))
127 | e = code.ErrServer
128 | return
129 | }
130 | }
131 | return
132 | }
133 |
--------------------------------------------------------------------------------
/app/http/resource/Common.go:
--------------------------------------------------------------------------------
1 | package resource
2 |
3 | // 通用Api列表返回数据
4 | type ApiList struct {
5 | List any `json:"list"`
6 | Total int64 `json:"total"`
7 | }
8 |
--------------------------------------------------------------------------------
/app/http/resource/User.go:
--------------------------------------------------------------------------------
1 | package resource
2 |
3 | import (
4 | "go-app/model"
5 | "time"
6 | )
7 |
8 | type Token struct {
9 | AccessToken string `json:"accessToken"`
10 | RefreshToken string `json:"refreshToken"`
11 | Expires time.Time `json:"expires"`
12 | }
13 |
14 | type Login struct {
15 | Username string `json:"username"`
16 | Roles model.StrArr `json:"roles"`
17 | Token
18 | }
19 |
--------------------------------------------------------------------------------
/app/server/apiServer.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "fmt"
5 | "go-app/app/http/middleware"
6 | "go-app/config"
7 | "go-app/lib/logger"
8 | "go-app/router"
9 |
10 | "github.com/gin-gonic/gin"
11 | "go.uber.org/zap"
12 | )
13 |
14 | type ApiServer struct {
15 | engine *gin.Engine
16 | }
17 |
18 | func (s *ApiServer) Start() {
19 | logger.Info("[Gin]", zap.String("status", "Api Server Start"))
20 | s.engine.Run(fmt.Sprintf(":%d", config.APP.Port))
21 | }
22 |
23 | func NewApiServer(engine *gin.Engine) *ApiServer {
24 | // 注册特有中间件
25 | engine.Use(middleware.Cros())
26 | // 注册路由
27 | router.RegisterApiRouter(engine)
28 |
29 | return &ApiServer{
30 | engine: engine,
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/server/gin.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "go-app/app/http/middleware"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | func NewGinEngine() (engine *gin.Engine) {
10 | // 一般来说不需要开启gin的debug模式
11 | // gin.SetMode(gin.DebugMode)
12 | gin.SetMode(gin.ReleaseMode)
13 | engine = gin.New()
14 | // 注册全局异常处理
15 | engine.Use(middleware.Recover)
16 | return
17 | }
18 |
--------------------------------------------------------------------------------
/app/server/webServer.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "fmt"
5 | "go-app/config"
6 | "go-app/lib/logger"
7 | "go-app/router"
8 |
9 | "github.com/gin-gonic/gin"
10 | "go.uber.org/zap"
11 | )
12 |
13 | type webServer struct {
14 | engine *gin.Engine
15 | }
16 |
17 | func (s *webServer) Start() {
18 | logger.Info("[Gin]", zap.String("status", "Web Server Start"))
19 | s.engine.Run(fmt.Sprintf(":%d", config.APP.Port))
20 | }
21 |
22 | func NewWebServer(engine *gin.Engine) *webServer {
23 | engine.SetTrustedProxies(nil)
24 | // 注册路由
25 | router.RegisterWebRouter(engine)
26 |
27 | return &webServer{
28 | engine: engine,
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/asset/embed.go:
--------------------------------------------------------------------------------
1 | package asset
2 |
3 | import (
4 | "embed"
5 | )
6 |
7 | //go:embed views
8 | var ViewFS embed.FS
9 |
10 | //go:embed public
11 | var PublicFS embed.FS
12 |
--------------------------------------------------------------------------------
/asset/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/asset/public/static/css/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 0px;
3 | margin: 0px;
4 | font-family: arial, helvetica, sans-serif, verdana, 'Open Sans';
5 | background: #39414a;
6 | }
--------------------------------------------------------------------------------
/asset/public/static/img/API.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anerg2046/go-admin-server/bb7268d1baca38f7ccf623dde237189e8e1a7074/asset/public/static/img/API.png
--------------------------------------------------------------------------------
/asset/public/static/js/app.js:
--------------------------------------------------------------------------------
1 | function help() {
2 | alert("js test")
3 | }
--------------------------------------------------------------------------------
/asset/views/index.html:
--------------------------------------------------------------------------------
1 | {{define "index.html"}}
2 |
3 |
4 |
5 | {{ template "layout/head.html" . }}
6 |
7 |
8 | 
9 | {{ .ip }}
10 |
11 | {{ template "layout/footer.html" . }}
12 |
13 |
14 |
15 | {{ end }}
--------------------------------------------------------------------------------
/asset/views/layout/footer.html:
--------------------------------------------------------------------------------
1 | {{ define "layout/footer.html" }}
2 |
3 | this is footer
4 | {{ end }}
--------------------------------------------------------------------------------
/asset/views/layout/head.html:
--------------------------------------------------------------------------------
1 | {{ define "layout/head.html" }}
2 |
3 |
4 |
5 | TEST
6 |
7 |
8 |
9 | {{ end }}
--------------------------------------------------------------------------------
/boot/db/database.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "fmt"
5 | "go-app/config"
6 | "go-app/lib/database"
7 | "go-app/lib/logger"
8 |
9 | "gorm.io/gorm"
10 | )
11 |
12 | var Conn *gorm.DB
13 |
14 | func init() {
15 |
16 | var err error
17 |
18 | // 如果有多个数据库链接需求,或者有主从,负载均衡等
19 | // InitMultiDatabase()
20 | // Conn.Logger = logger.NewGormLogger()
21 | // // 创建数据表
22 | // createTable()
23 |
24 | // 如果只是单一数据库
25 | Conn, err = database.ConnDB(config.DB_APP.DSN, config.DB_APP.DB_TYPE)
26 | if err != nil {
27 | panic(err)
28 | }
29 | Conn.Logger = logger.NewGormLogger()
30 | }
31 |
32 | // postgres 添加json数据的gin索引
33 | func addGinIndex(Conn *gorm.DB, tableName, fieldName string) {
34 | indexName := "idx_" + tableName + "_" + fieldName
35 | createIndexStatement := fmt.Sprintf("CREATE INDEX IF NOT EXISTS %s ON %s USING gin (%s)", indexName, tableName, fieldName)
36 | Conn.Exec(createIndexStatement)
37 | }
38 |
39 | func InitMultiDatabase() {
40 | // 具体可参考 https://github.com/go-gorm/dbresolver
41 | // var err error
42 | // Conn, err = gorm.Open(database.GenDialector(config.DB_APP.DSN, database.POSTGRES), &gorm.Config{
43 | // DisableForeignKeyConstraintWhenMigrating: true,
44 | // PrepareStmt: true,
45 | // })
46 | // if err != nil {
47 | // panic(err)
48 | // }
49 | // // 设置主库的线程池
50 | // sqlDB, err := Conn.DB()
51 | // sqlDB.SetMaxIdleConns(config.Pool.MaxIdleConns)
52 | // sqlDB.SetMaxOpenConns(config.Pool.MaxOpenConns)
53 | // sqlDB.SetConnMaxIdleTime(config.Pool.ConnMaxIdleTime)
54 | // sqlDB.SetConnMaxLifetime(config.Pool.ConnMaxLifetime)
55 | // if err != nil {
56 | // panic(err)
57 | // }
58 |
59 | // // 这里指定特定的表去特定的数据库
60 | // // 还可以设置从库,负载均衡等
61 | // slover := dbresolver.Register(
62 | // dbresolver.Config{
63 | // Sources: []gorm.Dialector{database.GenDialector(config.DB_DATA.DSN, database.POSTGRES)},
64 | // },
65 | // &model.Brand{},
66 | // &model.Coowner{},
67 | // &model.Flow{},
68 | // &model.FlowLast{},
69 | // &model.Global{},
70 | // &model.Item{},
71 | // &model.Owner{},
72 | // // &model.OwnerHistory{},
73 | // ).Register(
74 | // dbresolver.Config{
75 | // Sources: []gorm.Dialector{database.GenDialector(config.DB_FLOW.DSN, database.POSTGRES)},
76 | // },
77 | // &model.TmFlowStatus{},
78 | // ).Register(
79 | // dbresolver.Config{
80 | // Sources: []gorm.Dialector{database.GenDialector(config.DB_BUSINESS.DSN, database.POSTGRES)},
81 | // },
82 | // &model.BusinessCompany{},
83 | // &model.Business{},
84 | // &model.BusinessNode{},
85 | // &model.BusinessSendHistory{},
86 | // &model.BusinessTakeHistory{},
87 | // )
88 |
89 | // // 设置连接池信息
90 | // slover.SetConnMaxIdleTime(config.Pool.ConnMaxIdleTime).
91 | // SetConnMaxLifetime(config.Pool.ConnMaxLifetime).
92 | // SetMaxIdleConns(config.Pool.MaxIdleConns).
93 | // SetMaxOpenConns(config.Pool.MaxOpenConns)
94 |
95 | // Conn.Use(slover)
96 | }
97 |
--------------------------------------------------------------------------------
/cmd/api/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | // 必须在import第一位,否则无法获取正确的配置
5 | _ "github.com/sakirsensoy/genv/dotenv/autoload"
6 |
7 | "go-app/app/server"
8 | "go-app/config"
9 | "go-app/lib/validator"
10 | )
11 |
12 | func main() {
13 | config.APP.Mode = config.MODE_API
14 | validator.NewValidator()
15 | app := server.NewApiServer(server.NewGinEngine())
16 | app.Start()
17 | }
18 |
--------------------------------------------------------------------------------
/cmd/migrator/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | // 必须在import第一位,否则无法获取正确的配置
5 | "time"
6 |
7 | _ "github.com/sakirsensoy/genv/dotenv/autoload"
8 | "github.com/wumansgy/goEncrypt/hash"
9 | "gorm.io/gorm"
10 |
11 | "go-app/app/enums"
12 | "go-app/boot/db"
13 | "go-app/config"
14 | "go-app/lib/database"
15 | "go-app/lib/rbac"
16 | "go-app/model"
17 | )
18 |
19 | func main() {
20 | conn, _ := database.ConnDB(config.DB_APP.DSN, config.DB_APP.DB_TYPE)
21 | app(conn)
22 | defaultMenu(conn)
23 | defaultRole(conn)
24 | defaultUser(conn)
25 | }
26 |
27 | func app(conn *gorm.DB) {
28 | conn.AutoMigrate(
29 | &model.User{},
30 | &model.UserToken{},
31 | &model.Menu{},
32 | &model.Role{},
33 | )
34 | }
35 |
36 | func defaultMenu(conn *gorm.DB) {
37 | var count int64
38 | db.Conn.Model(&model.Menu{}).Count(&count)
39 | if count > 0 {
40 | return
41 | }
42 | menu := []model.Menu{
43 | {
44 | Genre: enums.Menu_MENU,
45 | Path: "/sys",
46 | Name: "SystemSetting",
47 | Queue: 0,
48 | Meta: model.MenuMeta{
49 | Title: "系统管理",
50 | Icon: "solar:settings-linear",
51 | ShowLink: true,
52 | },
53 | Children: []model.Menu{
54 | {
55 | Genre: enums.Menu_MENU,
56 | Path: "/sys/menu/index",
57 | Name: "SysMenuManagement",
58 | Queue: 0,
59 | Meta: model.MenuMeta{
60 | Title: "菜单管理",
61 | KeepAlive: true,
62 | ShowParent: true,
63 | ShowLink: true,
64 | },
65 | Children: []model.Menu{
66 | {
67 | Genre: enums.Menu_ACTION,
68 | Path: "/sys/menus/list",
69 | Name: "ActMenuList",
70 | Meta: model.MenuMeta{
71 | Title: "菜单-列表",
72 | KeepAlive: false,
73 | ShowParent: true,
74 | ShowLink: false,
75 | },
76 | },
77 | },
78 | },
79 | {
80 | Genre: enums.Menu_MENU,
81 | Path: "/sys/roles",
82 | Component: "/sys/role/index",
83 | Name: "SysRoleManagement",
84 | Queue: 5,
85 | Meta: model.MenuMeta{
86 | Title: "角色管理",
87 | KeepAlive: true,
88 | ShowParent: true,
89 | ShowLink: true,
90 | },
91 | },
92 | {
93 | Genre: enums.Menu_MENU,
94 | Path: "/sys/users",
95 | Component: "/sys/user/index",
96 | Name: "SysUserManagement",
97 | Queue: 10,
98 | Meta: model.MenuMeta{
99 | Title: "用户管理",
100 | KeepAlive: true,
101 | ShowParent: true,
102 | ShowLink: true,
103 | },
104 | Children: []model.Menu{
105 | {
106 | Genre: enums.Menu_ACTION,
107 | Path: "/sys/users/list",
108 | Name: "ActUserList",
109 | Meta: model.MenuMeta{
110 | Title: "用户-列表",
111 | KeepAlive: false,
112 | ShowParent: true,
113 | ShowLink: false,
114 | },
115 | },
116 | },
117 | },
118 | },
119 | },
120 | }
121 | conn.Save(&menu)
122 | }
123 |
124 | func defaultRole(conn *gorm.DB) {
125 | var count int64
126 | conn.Model(&model.Role{}).Where(&model.Role{Code: "superadmin"}).Count(&count)
127 | if count == 0 {
128 | role := []model.Role{
129 | {
130 | Code: "superadmin",
131 | Name: "超级管理员",
132 | Queue: 0,
133 | Description: "拥有所有权限,且不可删除",
134 | },
135 | {
136 | Code: "user",
137 | Name: "注册用户",
138 | Queue: 5,
139 | Description: "普通用户,只拥有普通权限",
140 | },
141 | }
142 | conn.Create(&role)
143 | }
144 | }
145 |
146 | func defaultUser(conn *gorm.DB) {
147 | var count int64
148 | db.Conn.Model(&model.User{}).Count(&count)
149 | if count > 0 {
150 | return
151 | }
152 |
153 | users := []model.User{
154 | {
155 | UserApi: model.UserApi{
156 | Username: "admin",
157 | Phone: "13000000000",
158 | CreatedAt: time.Now(),
159 | },
160 | Password: hash.HmacSha256Hex([]byte(config.HASH.HmacSha256Key), "admin1234"),
161 | },
162 | {
163 | UserApi: model.UserApi{
164 | Username: "test",
165 | Phone: "13100000000",
166 | CreatedAt: time.Now(),
167 | },
168 | Password: hash.HmacSha256Hex([]byte(config.HASH.HmacSha256Key), "test123456"),
169 | },
170 | }
171 |
172 | conn.Create(&users)
173 |
174 | rbac.New().AddRoleForUser("admin", "superadmin")
175 | rbac.New().AddRoleForUser("test", "user")
176 | }
177 |
--------------------------------------------------------------------------------
/cmd/test/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | _ "github.com/sakirsensoy/genv/dotenv/autoload"
5 |
6 | "go-app/lib/rbac"
7 | )
8 |
9 | func main() {
10 | rbac.New().AddRoleForUser("废墟", "superadmin")
11 | // var user model.User
12 |
13 | // user.ID = 1
14 | // user.Password = hash.HmacSha256Hex([]byte(config.HASH.HmacSha256Key), "admin123")
15 | // db.Conn.Save(&user)
16 | }
17 |
--------------------------------------------------------------------------------
/cmd/web/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | // 必须在import第一位,否则无法获取正确的配置
5 | _ "github.com/sakirsensoy/genv/dotenv/autoload"
6 | "go.uber.org/zap"
7 |
8 | "go-app/app/server"
9 | "go-app/asset"
10 | "go-app/config"
11 | "go-app/lib/embedfs"
12 | "go-app/lib/logger"
13 | "go-app/lib/validator"
14 | "html/template"
15 |
16 | "github.com/gin-contrib/static"
17 | "github.com/gin-gonic/gin"
18 | )
19 |
20 | func main() {
21 | config.APP.Mode = config.MODE_WEB
22 | validator.NewValidator()
23 |
24 | engin := server.NewGinEngine()
25 |
26 | engin.Use(static.Serve("/", embedfs.EmbedFolder(asset.PublicFS, "public", false)))
27 | engin.StaticFS("/upload", gin.Dir("./upload", false))
28 |
29 | engin.SetHTMLTemplate(parseAssetTemplates())
30 |
31 | app := server.NewWebServer(engin)
32 | app.Start()
33 |
34 | }
35 |
36 | // 模板公共方法
37 | func templateFuncs() template.FuncMap {
38 | return template.FuncMap{}
39 | }
40 |
41 | // 从embed读取静态资源
42 | func assetReader(path string, templ *template.Template) {
43 | fs, _ := asset.ViewFS.ReadDir(path)
44 | for _, f := range fs {
45 | if f.IsDir() {
46 | assetReader(path+"/"+f.Name(), templ)
47 | } else {
48 | content, _ := asset.ViewFS.ReadFile(path + "/" + f.Name())
49 | _, err := templ.Parse(string(content))
50 | if err != nil {
51 | logger.Error("[EMBED VIEW]", zap.Error(err))
52 | }
53 | }
54 | }
55 | }
56 |
57 | // 从embed读取模板文件
58 | func parseAssetTemplates() *template.Template {
59 | templ := template.New("").Funcs(templateFuncs())
60 | assetReader("views", templ)
61 | return templ
62 | }
63 |
--------------------------------------------------------------------------------
/config/app.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/sakirsensoy/genv"
7 | )
8 |
9 | type APP_MODE uint16
10 |
11 | const (
12 | _ APP_MODE = iota
13 | MODE_WEB
14 | MODE_API
15 | )
16 |
17 | type appConf struct {
18 | Debug bool
19 | Env string
20 | Mode APP_MODE // 运行模式 web 或者 api 无需在配置文件配置
21 | Timezone *time.Location
22 | Port int
23 | }
24 |
25 | var APP *appConf
26 |
27 | func init() {
28 | APP = &appConf{
29 | Debug: genv.Key("APP_DEBUG").Default(false).Bool(),
30 | Timezone: getTimezone(),
31 | Port: genv.Key("APP_PORT").Default(8020).Int(),
32 | }
33 | }
34 |
35 | func getTimezone() (timezone *time.Location) {
36 | timezone, _ = time.LoadLocation(genv.Key("APP_TIMEZONE").Default("PRC").String())
37 | return
38 | }
39 |
--------------------------------------------------------------------------------
/config/casbin.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | casbinmodel "github.com/casbin/casbin/v2/model"
5 | "github.com/sakirsensoy/genv"
6 | )
7 |
8 | type casbinConf struct {
9 | Model casbinmodel.Model
10 | DriverName string
11 | DataSourceName string
12 | }
13 |
14 | var CASBIN = &casbinConf{
15 | DriverName: genv.Key("CASBIN_DRIVER").Default("postgres").String(),
16 | DataSourceName: genv.Key("CASBIN_DSN").Default(DB_APP.DSN).String(),
17 | }
18 |
--------------------------------------------------------------------------------
/config/cloudflare.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/sakirsensoy/genv"
5 | )
6 |
7 | type cloudflareConf struct {
8 | TurnstileSecret string
9 | TurnstileUrl string
10 | }
11 |
12 | var CLOUDFLARE = &cloudflareConf{
13 | TurnstileSecret: genv.Key("CLOUDFLARE_TURNSTILE_SECRET").Default("12345").String(),
14 | TurnstileUrl: genv.Key("CLOUDFLARE_TURNSTILE_URL").Default("https://challenges.cloudflare.com/turnstile/v0/siteverify").String(),
15 | }
16 |
--------------------------------------------------------------------------------
/config/db.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "go-app/lib/util"
5 |
6 | "github.com/sakirsensoy/genv"
7 | )
8 |
9 | type DBTYPE uint16
10 |
11 | const (
12 | _ DBTYPE = iota
13 | DBTYPE_MYSQL
14 | DBTYPE_MSSQL
15 | DBTYPE_POSTGRES
16 | )
17 |
18 | type dbConf struct {
19 | DSN string
20 | DB_TYPE DBTYPE
21 | }
22 |
23 | var DB_APP = &dbConf{
24 | DSN: util.PostgresDSN(
25 | genv.Key("DB_APP_HOST").Default("postgres").String(),
26 | genv.Key("DB_APP_USER").Default("root").String(),
27 | genv.Key("DB_APP_PASS").Default("111222").String(),
28 | genv.Key("DB_APP_PORT").Default("5432").String(),
29 | genv.Key("DB_APP_DBNAME").Default("go_admin").String(),
30 | ),
31 | DB_TYPE: DBTYPE_POSTGRES,
32 | }
33 |
--------------------------------------------------------------------------------
/config/dbPool.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/sakirsensoy/genv"
7 | )
8 |
9 | // 数据库线程池设置
10 | type poolConf struct {
11 |
12 | // 连接池里面的连接最大空闲时长
13 | //
14 | // 一般不做修改
15 | ConnMaxIdleTime time.Duration
16 |
17 | // 连接池里面的连接最大存活时长
18 | //
19 | // 必须要比MySQL服务器设置的wait_timeout小
20 | //
21 | // MySQL一般默认是8小时
22 | //
23 | // 一般不做修改
24 | ConnMaxLifetime time.Duration
25 |
26 | // 连接池里最大空闲连接数
27 | //
28 | // 必须要比MaxOpenConns小
29 | MaxIdleConns int
30 |
31 | // 连接池最多同时打开的连接数
32 | //
33 | // 应当比数据库服务器的max_connections值要小
34 | //
35 | // 一般设置为: 服务器cpu核心数 * 2 + 服务器有效磁盘数
36 | MaxOpenConns int
37 | }
38 |
39 | var Pool = &poolConf{
40 | ConnMaxIdleTime: 30 * time.Minute,
41 | ConnMaxLifetime: 2 * time.Hour,
42 | MaxIdleConns: genv.Key("DB_MAX_IDLE_CONNS").Default(4).Int(),
43 | MaxOpenConns: genv.Key("DB_MAX_OPEN_CONNS").Default(128).Int(),
44 | }
45 |
--------------------------------------------------------------------------------
/config/hash.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "github.com/sakirsensoy/genv"
4 |
5 | type hashConf struct {
6 | HmacSha256Key string
7 | AesKey string
8 | AesIV string
9 | }
10 |
11 | var HASH = &hashConf{
12 | HmacSha256Key: genv.Key("HMAC_SHA256_KEY").Default("MzU0OTkxNzk0NDI1").String(),
13 | AesKey: genv.Key("HASH_AES_KEY").Default("6EjDpd8QPnkKXWVC").String(),
14 | AesIV: genv.Key("HASH_AES_IV").Default("h8JLYxUk9svkkMad").String(),
15 | }
16 |
--------------------------------------------------------------------------------
/config/httpclient.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "github.com/sakirsensoy/genv"
4 |
5 | type httpclientConf struct {
6 | Debug bool
7 | RetryCount int
8 | Timeout int
9 | }
10 |
11 | var HTTPCLIENT = &httpclientConf{
12 | Debug: genv.Key("HTTP_DEBUG").Default(false).Bool(),
13 | RetryCount: genv.Key("HTTP_RETRY_COUNT").Default(3).Int(),
14 | Timeout: genv.Key("HTTP_TIMEOUT").Default(30).Int(),
15 | }
16 |
--------------------------------------------------------------------------------
/config/jwt.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/sakirsensoy/genv"
5 |
6 | "github.com/golang-jwt/jwt"
7 | )
8 |
9 | type jwtConf struct {
10 | Key string
11 | HeaderField string
12 | RedirectUrl string
13 | }
14 |
15 | // 载荷,可以加一些自己需要的信息
16 | type JwtClaims struct {
17 | ID uint `json:"id,omitempty"`
18 | Username string `json:"username,omitempty"`
19 | jwt.StandardClaims
20 | }
21 |
22 | var JWT = &jwtConf{
23 | Key: genv.Key("JWT_KEY").Default("5fqwcChUsM*g@F2G").String(),
24 | HeaderField: genv.Key("JWT_HEADER_FIELD").Default("Authorization").String(),
25 | RedirectUrl: genv.Key("JWT_REDIRECT_URL").Default("/login").String(),
26 | }
27 |
--------------------------------------------------------------------------------
/config/log.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "github.com/sakirsensoy/genv"
4 |
5 | type logConf struct {
6 | Level string
7 | Type string
8 | Path string
9 | File string
10 | MaxSize int
11 | MaxBackup int
12 | MaxAge int
13 | Compress bool
14 | }
15 |
16 | var LOG = &logConf{
17 | Level: genv.Key("LOG_LEVEL").Default("error").String(),
18 | Type: genv.Key("LOG_TYPE").Default("stdout").String(),
19 | Path: genv.Key("LOG_PATH").Default("storage/logs/").String(),
20 | File: genv.Key("LOG_FILE").Default("logs.log").String(),
21 | MaxSize: genv.Key("LOG_MAX_SIZE").Default(64).Int(),
22 | MaxBackup: genv.Key("LOG_MAX_BACKUP").Default(10).Int(),
23 | MaxAge: genv.Key("LOG_MAX_AGE").Default(7).Int(),
24 | Compress: genv.Key("LOG_COMPRESS").Default(false).Bool(),
25 | }
26 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module go-app
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/casbin/casbin/v2 v2.60.0
7 | github.com/casbin/gorm-adapter/v3 v3.13.1
8 | github.com/gin-contrib/cors v1.4.0
9 | github.com/gin-contrib/static v0.0.1
10 | github.com/gin-gonic/gin v1.8.1
11 | github.com/go-playground/locales v0.14.0
12 | github.com/go-playground/universal-translator v0.18.0
13 | github.com/go-playground/validator/v10 v10.10.0
14 | github.com/go-resty/resty/v2 v2.7.0
15 | github.com/goccy/go-json v0.9.7
16 | github.com/golang-jwt/jwt v3.2.2+incompatible
17 | github.com/jlaffaye/ftp v0.1.0
18 | github.com/olivere/elastic/v6 v6.2.1
19 | github.com/olivere/elastic/v7 v7.0.32
20 | github.com/sakirsensoy/genv v1.0.1
21 | github.com/streadway/amqp v1.0.0
22 | github.com/wumansgy/goEncrypt v1.1.0
23 | go.uber.org/zap v1.13.0
24 | golang.org/x/text v0.3.7
25 | gopkg.in/natefinch/lumberjack.v2 v2.0.0
26 | gorm.io/driver/mysql v1.4.1
27 | gorm.io/driver/postgres v1.4.4
28 | gorm.io/driver/sqlserver v1.4.1
29 | gorm.io/gorm v1.24.0
30 | )
31 |
32 | require (
33 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
34 | github.com/gin-contrib/sse v0.1.0 // indirect
35 | github.com/glebarez/go-sqlite v1.19.1 // indirect
36 | github.com/glebarez/sqlite v1.5.0 // indirect
37 | github.com/go-sql-driver/mysql v1.6.0 // indirect
38 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
39 | github.com/golang-sql/sqlexp v0.1.0 // indirect
40 | github.com/google/uuid v1.3.0 // indirect
41 | github.com/hashicorp/errwrap v1.0.0 // indirect
42 | github.com/hashicorp/go-multierror v1.1.1 // indirect
43 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
44 | github.com/jackc/pgconn v1.13.0 // indirect
45 | github.com/jackc/pgio v1.0.0 // indirect
46 | github.com/jackc/pgpassfile v1.0.0 // indirect
47 | github.com/jackc/pgproto3/v2 v2.3.1 // indirect
48 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
49 | github.com/jackc/pgtype v1.12.0 // indirect
50 | github.com/jackc/pgx/v4 v4.17.2 // indirect
51 | github.com/jinzhu/inflection v1.0.0 // indirect
52 | github.com/jinzhu/now v1.1.5 // indirect
53 | github.com/josharian/intern v1.0.0 // indirect
54 | github.com/json-iterator/go v1.1.12 // indirect
55 | github.com/leodido/go-urn v1.2.1 // indirect
56 | github.com/mailru/easyjson v0.7.7 // indirect
57 | github.com/mattn/go-isatty v0.0.16 // indirect
58 | github.com/microsoft/go-mssqldb v0.17.0 // indirect
59 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
60 | github.com/modern-go/reflect2 v1.0.2 // indirect
61 | github.com/olivere/elastic v6.2.37+incompatible // indirect
62 | github.com/pelletier/go-toml/v2 v2.0.1 // indirect
63 | github.com/pkg/errors v0.9.1 // indirect
64 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
65 | github.com/ugorji/go/codec v1.2.7 // indirect
66 | go.uber.org/atomic v1.6.0 // indirect
67 | go.uber.org/multierr v1.5.0 // indirect
68 | golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b // indirect
69 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect
70 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
71 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
72 | golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 // indirect
73 | google.golang.org/protobuf v1.28.0 // indirect
74 | gopkg.in/yaml.v2 v2.4.0 // indirect
75 | gorm.io/plugin/dbresolver v1.3.0 // indirect
76 | modernc.org/libc v1.19.0 // indirect
77 | modernc.org/mathutil v1.5.0 // indirect
78 | modernc.org/memory v1.4.0 // indirect
79 | modernc.org/sqlite v1.19.1 // indirect
80 | )
81 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
2 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM=
3 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
4 | github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
5 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
6 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
7 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
8 | github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
9 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
10 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
11 | github.com/casbin/casbin/v2 v2.55.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
12 | github.com/casbin/casbin/v2 v2.60.0 h1:ZmC0/t4wolfEsDpDxTEsu2z6dfbMNpc11F52ceLs2Eo=
13 | github.com/casbin/casbin/v2 v2.60.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
14 | github.com/casbin/gorm-adapter/v3 v3.13.1 h1:0AuYv5WYihjHEZyPYDxCBpyCDwvJsVTm6VUXmT/Tgvs=
15 | github.com/casbin/gorm-adapter/v3 v3.13.1/go.mod h1:jqaf4bUITbCyMPUellaTd8IQJ77JfVAbe77gZZnx98w=
16 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
17 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
18 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
19 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
20 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
21 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
22 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
23 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
24 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
25 | github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
26 | github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
27 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
28 | github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
29 | github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
30 | github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
31 | github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
32 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
33 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
34 | github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
35 | github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
36 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
37 | github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
38 | github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
39 | github.com/glebarez/go-sqlite v1.19.1 h1:o2XhjyR8CQ2m84+bVz10G0cabmG0tY4sIMiCbrcUTrY=
40 | github.com/glebarez/go-sqlite v1.19.1/go.mod h1:9AykawGIyIcxoSfpYWiX1SgTNHTNsa/FVc75cDkbp4M=
41 | github.com/glebarez/sqlite v1.5.0 h1:+8LAEpmywqresSoGlqjjT+I9m4PseIM3NcerIJ/V7mk=
42 | github.com/glebarez/sqlite v1.5.0/go.mod h1:0wzXzTvfVJIN2GqRhCdMbnYd+m+aH5/QV7B30rM6NgY=
43 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
44 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
45 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
46 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
47 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
48 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
49 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
50 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
51 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
52 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
53 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
54 | github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
55 | github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
56 | github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
57 | github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
58 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
59 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
60 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
61 | github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
62 | github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
63 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
64 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
65 | github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
66 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
67 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
68 | github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
69 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
70 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
71 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
72 | github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
73 | github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
74 | github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
75 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
76 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
77 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
78 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
79 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
80 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
81 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
82 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
83 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
84 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
85 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
86 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
87 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
88 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
89 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
90 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
91 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
92 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
93 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
94 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
95 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
96 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
97 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
98 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
99 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
100 | github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
101 | github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
102 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
103 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
104 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
105 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
106 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
107 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
108 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
109 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
110 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
111 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
112 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
113 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
114 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
115 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
116 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
117 | github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
118 | github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
119 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
120 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
121 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
122 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
123 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
124 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
125 | github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
126 | github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
127 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
128 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
129 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
130 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
131 | github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
132 | github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
133 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
134 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
135 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
136 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
137 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
138 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
139 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
140 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
141 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
142 | github.com/jlaffaye/ftp v0.1.0 h1:DLGExl5nBoSFoNshAUHwXAezXwXBvFdx7/qwhucWNSE=
143 | github.com/jlaffaye/ftp v0.1.0/go.mod h1:hhq4G4crv+nW2qXtNYcuzLeOudG92Ps37HEKeg2e3lE=
144 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
145 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
146 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
147 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
148 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
149 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
150 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
151 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
152 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
153 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
154 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
155 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
156 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
157 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
158 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
159 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
160 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
161 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
162 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
163 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
164 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
165 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
166 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
167 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
168 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
169 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
170 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
171 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
172 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
173 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
174 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
175 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
176 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
177 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
178 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
179 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
180 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
181 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
182 | github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
183 | github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
184 | github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
185 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
186 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
187 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
188 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
189 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
190 | github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
191 | github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
192 | github.com/olivere/elastic v6.2.37+incompatible h1:UfSGJem5czY+x/LqxgeCBgjDn6St+z8OnsCuxwD3L0U=
193 | github.com/olivere/elastic v6.2.37+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8=
194 | github.com/olivere/elastic/v6 v6.2.1 h1:tZ2NZWoFCdFnuQg1q9JCyjN6YTczNF03tLj954ptqNc=
195 | github.com/olivere/elastic/v6 v6.2.1/go.mod h1:OeCPPyGCIn9j7/1Dk+tGE7gsezYo9lsJIiHhZjT/qQ4=
196 | github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E=
197 | github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k=
198 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
199 | github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
200 | github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
201 | github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
202 | github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
203 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
204 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
205 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
206 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
207 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
208 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
209 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
210 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
211 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
212 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
213 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
214 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
215 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
216 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
217 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
218 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
219 | github.com/sakirsensoy/genv v1.0.1 h1:zCZNLHgEM4ZJSyQRArqOdFYqj5uq5FaA/aiBh4UPskU=
220 | github.com/sakirsensoy/genv v1.0.1/go.mod h1:spzy6aws2VhPHh33C3sAfREGcXiEGbB1o6j0p2efJws=
221 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
222 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
223 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
224 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
225 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
226 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
227 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
228 | github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
229 | github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo=
230 | github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
231 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
232 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
233 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
234 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
235 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
236 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
237 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
238 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
239 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
240 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
241 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
242 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
243 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
244 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
245 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
246 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
247 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
248 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
249 | github.com/wumansgy/goEncrypt v1.1.0 h1:Krr2FJL4GEsMTBvLfsnoTmgWb7rkGnL4siJ9K2cxMs0=
250 | github.com/wumansgy/goEncrypt v1.1.0/go.mod h1:dWgF7mi5Ujmt8V5EoyRqjH6XtZ8wmNQyT4u2uvH8Pyg=
251 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
252 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
253 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
254 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
255 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
256 | go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
257 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
258 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
259 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
260 | go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
261 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
262 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
263 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
264 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
265 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
266 | go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
267 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
268 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
269 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
270 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
271 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
272 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
273 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
274 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
275 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
276 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
277 | golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
278 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
279 | golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0=
280 | golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
281 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
282 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
283 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
284 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
285 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
286 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
287 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
288 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
289 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
290 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
291 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
292 | golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
293 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
294 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
295 | golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
296 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
297 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
298 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
299 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
300 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
301 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
302 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
303 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
304 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
305 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
306 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
307 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
308 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
309 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
310 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
311 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
312 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
313 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
314 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
315 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
316 | golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
317 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
318 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
319 | golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
320 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
321 | golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
322 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
323 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
324 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
325 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
326 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
327 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
328 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
329 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
330 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
331 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
332 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
333 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
334 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
335 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
336 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
337 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
338 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
339 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
340 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
341 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
342 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
343 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
344 | golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
345 | golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
346 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
347 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
348 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
349 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
350 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
351 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
352 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
353 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
354 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
355 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
356 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
357 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
358 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
359 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
360 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
361 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
362 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
363 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
364 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
365 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
366 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
367 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
368 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
369 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
370 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
371 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
372 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
373 | gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
374 | gorm.io/driver/mysql v1.4.1 h1:4InA6SOaYtt4yYpV1NF9B2kvUKe9TbvUd1iWrvxnjic=
375 | gorm.io/driver/mysql v1.4.1/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
376 | gorm.io/driver/postgres v1.4.4 h1:zt1fxJ+C+ajparn0SteEnkoPg0BQ6wOWXEQ99bteAmw=
377 | gorm.io/driver/postgres v1.4.4/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw=
378 | gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
379 | gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=
380 | gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
381 | gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
382 | gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
383 | gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74=
384 | gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
385 | gorm.io/plugin/dbresolver v1.3.0 h1:uFDX3bIuH9Lhj5LY2oyqR/bU6pqWuDgas35NAPF4X3M=
386 | gorm.io/plugin/dbresolver v1.3.0/go.mod h1:Pr7p5+JFlgDaiM6sOrli5olekJD16YRunMyA2S7ZfKk=
387 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
388 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
389 | lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
390 | modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
391 | modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
392 | modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=
393 | modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI=
394 | modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0=
395 | modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
396 | modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
397 | modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
398 | modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
399 | modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA=
400 | modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0=
401 | modernc.org/libc v1.19.0 h1:bXyVhGQg6KIClTr8FMVIDPl7jtbcs7aS5WP7vLDaxPs=
402 | modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0=
403 | modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
404 | modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
405 | modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
406 | modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
407 | modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
408 | modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
409 | modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
410 | modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
411 | modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
412 | modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
413 | modernc.org/sqlite v1.19.1 h1:8xmS5oLnZtAK//vnd4aTVj8VOeTAccEFOtUnIzfSw+4=
414 | modernc.org/sqlite v1.19.1/go.mod h1:UfQ83woKMaPW/ZBruK0T7YaFCrI+IE0LeWVY6pmnVms=
415 | modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
416 | modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
417 | modernc.org/tcl v1.14.0/go.mod h1:gQ7c1YPMvryCHCcmf8acB6VPabE59QBeuRQLL7cTUlM=
418 | modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
419 | modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
420 | modernc.org/z v1.6.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=
421 |
--------------------------------------------------------------------------------
/model/Menu.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "database/sql/driver"
5 | "errors"
6 | "fmt"
7 | "go-app/app/enums"
8 | "strings"
9 |
10 | "github.com/goccy/go-json"
11 | "gorm.io/gorm"
12 | )
13 |
14 | type Menu struct {
15 | ID uint `form:"id" json:"id,omitempty"`
16 | ParentID uint `form:"parent_id" json:"parent_id"`
17 | Genre enums.MenuGenre `form:"genre" json:"genre" binding:"required"`
18 | Path string `form:"path" json:"path,omitempty" binding:"required"` // 路由地址
19 | Component string `form:"component" json:"component,omitempty"` // 按需加载需要展示的页面
20 | Name string `gorm:"unique;<-:create" form:"name" json:"name,omitempty" binding:"required"` // 路由名字(必须保持唯一)
21 | Queue int `form:"queue" json:"queue"` // 菜单排序
22 | Meta MenuMeta `gorm:"type:json" form:"meta" json:"meta,omitempty"` // 路由元信息
23 | Children []Menu `gorm:"foreignkey:parent_id" json:"children,omitempty"` // 子路由
24 | }
25 |
26 | func (m *Menu) AfterFind(tx *gorm.DB) (err error) {
27 | m.Meta.Transition.EnterTransition = "animate__fadeIn animate__faster"
28 | m.Meta.Transition.LeaveTransition = "animate__fadeOut animate__faster"
29 | return
30 | }
31 |
32 | func (m *Menu) BeforeDelete(tx *gorm.DB) (err error) {
33 | if strings.Contains(m.Path, "/sys") {
34 | return errors.New("系统菜单禁止删除")
35 | }
36 | return
37 | }
38 |
39 | func (m *Menu) BeforeUpdate(tx *gorm.DB) (err error) {
40 | if strings.Contains(m.Path, "/sys") {
41 | return errors.New("系统菜单禁止编辑")
42 | }
43 | return
44 | }
45 |
46 | // 路由元信息
47 | type MenuMeta struct {
48 | Title string `form:"title" json:"title,omitempty"` // 菜单名称
49 | Icon string `form:"icon" json:"icon,omitempty"` // 菜单图标
50 | ShowLink bool `form:"showLink" json:"showLink"` // 是否在菜单中显示
51 | ShowParent bool `form:"showParent" json:"showParent"` // 是否显示父级菜单
52 | KeepAlive bool `form:"keepAlive" json:"keepAlive"` // 是否缓存该路由页面(开启后,会保存该页面的整体状态,刷新后会清空状态)
53 | FrameSrc string `form:"frameSrc" json:"frameSrc,omitempty"` // 需要内嵌的iframe链接地址
54 | FrameLoading bool `form:"frameLoading" json:"frameLoading"` // 内嵌的iframe页面是否开启首次加载动画
55 | HiddenTag bool `json:"hiddenTag"` // 当前菜单名称或自定义信息禁止添加到标签页
56 | DynamicLevel int `json:"dynamicLevel,omitempty"` // 显示在标签页的最大数量,需满足后面的条件:不显示在菜单中的路由并且是通过query或params传参模式打开的页面
57 | Transition MenuMetaTransition `json:"transition,omitempty"` // 页面加载动画
58 | // Roles StrArr `form:"roles" json:"roles,omitempty"` // 页面级别权限设置
59 | // Auths StrArr `form:"auths" json:"auths,omitempty"` // 按钮级别权限设置
60 | }
61 |
62 | // 页面加载动画
63 | type MenuMetaTransition struct {
64 | EnterTransition string `json:"enterTransition,omitempty"` // 当前页面进场动画
65 | LeaveTransition string `json:"leaveTransition,omitempty"` // 当前页面离场动画
66 | }
67 |
68 | func (c MenuMeta) Value() (driver.Value, error) {
69 | b, err := json.Marshal(c)
70 | return string(b), err
71 | }
72 | func (c *MenuMeta) Scan(value any) error {
73 | bytes, ok := value.([]byte)
74 | if !ok {
75 | return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
76 | }
77 | return json.Unmarshal(bytes, c)
78 | }
79 |
80 | func (c MenuMetaTransition) Value() (driver.Value, error) {
81 | b, err := json.Marshal(c)
82 | return string(b), err
83 | }
84 | func (c *MenuMetaTransition) Scan(value any) error {
85 | bytes, ok := value.([]byte)
86 | if !ok {
87 | return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
88 | }
89 | return json.Unmarshal(bytes, c)
90 | }
91 |
--------------------------------------------------------------------------------
/model/Role.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "errors"
5 | "go-app/lib/rbac"
6 |
7 | "gorm.io/gorm"
8 | )
9 |
10 | // 角色模型
11 | type Role struct {
12 | ID uint `form:"id" json:"id" binding:"omitempty,gte=1" label:"角色ID"` // 角色id
13 | Code string `form:"code" json:"code" binding:"required" label:"角色编码"` // 角色编码
14 | Name string `form:"name" json:"name" binding:"required" label:"角色名称"` // 角色名称
15 | Queue uint `form:"queue" json:"queue" binding:"omitempty,gte=0" label:"角色排序"` // 排序
16 | Description string `form:"description" json:"description,omitempty"`
17 | }
18 |
19 | func (r *Role) BeforeDelete(tx *gorm.DB) (err error) {
20 | if r.Code == "user" || r.Code == "superadmin" {
21 | return errors.New("默认角色禁止删除")
22 | }
23 | rbac.New().DeleteRole(r.Code)
24 | rbac.New().DeletePermissionsForUser(r.Code)
25 | return
26 | }
27 |
--------------------------------------------------------------------------------
/model/User.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "errors"
5 | "go-app/lib/rbac"
6 | "time"
7 |
8 | "gorm.io/gorm"
9 | )
10 |
11 | type UserApi struct {
12 | ID uint `form:"id" json:"id,omitempty"`
13 | Username string `form:"username" gorm:"uniqueIndex" json:"username,omitempty"`
14 | Phone string `form:"phone" gorm:"uniqueIndex" json:"phone,omitempty"`
15 | CreatedAt time.Time `json:"created_at,omitempty"`
16 | Roles StrArr `gorm:"-" json:"roles"`
17 | }
18 |
19 | func (u *UserApi) AfterFind(tx *gorm.DB) (err error) {
20 | u.Roles, _ = rbac.New().GetRolesForUser(u.Username)
21 | return
22 | }
23 |
24 | type User struct {
25 | UserApi
26 | Password string `form:"password" json:"password,omitempty"`
27 | }
28 |
29 | func (u *User) AfterFind(tx *gorm.DB) (err error) {
30 | u.Roles, _ = rbac.New().GetRolesForUser(u.Username)
31 | return
32 | }
33 |
34 | func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
35 | if u.Username == "admin" || u.Username == "test" {
36 | return errors.New("默认用户禁止修改")
37 | }
38 | return
39 | }
40 |
41 | func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
42 | if u.Username == "admin" || u.Username == "test" {
43 | return errors.New("默认用户禁止删除")
44 | }
45 | rbac.New().DeleteRolesForUser(u.Username)
46 | return
47 | }
48 |
49 | type UserToken struct {
50 | ID uint `json:"id,omitempty"`
51 | Token string `gorm:"index" json:"token,omitempty"`
52 | ExpiredAt time.Time `json:"expired_at,omitempty"`
53 | User *User `gorm:"foreignKey:id" json:"user"`
54 | }
55 |
--------------------------------------------------------------------------------
/model/common.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "database/sql/driver"
5 | "errors"
6 | "fmt"
7 |
8 | "github.com/goccy/go-json"
9 | )
10 |
11 | // 字符串Json数组
12 | type StrArr []string
13 |
14 | func (c StrArr) Value() (driver.Value, error) {
15 | b, err := json.Marshal(c)
16 | return string(b), err
17 | }
18 | func (c *StrArr) Scan(value any) error {
19 | bytes, ok := value.([]byte)
20 | if !ok {
21 | return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
22 | }
23 | return json.Unmarshal(bytes, c)
24 | }
25 |
26 | type DbHookError error
27 |
--------------------------------------------------------------------------------
/model/scope/common.go:
--------------------------------------------------------------------------------
1 | package scope
2 |
3 | import (
4 | "go-app/app/http/binding"
5 |
6 | "gorm.io/gorm"
7 | )
8 |
9 | func Paginate(params binding.Pager) func(db *gorm.DB) *gorm.DB {
10 | return func(db *gorm.DB) *gorm.DB {
11 | offset := (params.Page - 1) * params.Size
12 | return db.Offset(offset).Limit(params.Size)
13 | }
14 | }
15 |
16 | // 展开所有的子集
17 | func ExpandChildren(db *gorm.DB) *gorm.DB {
18 | return db.Preload("Children", ExpandChildren)
19 | }
20 |
--------------------------------------------------------------------------------
/router/api.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "go-app/app/code"
5 | "go-app/app/http/binding"
6 | "go-app/app/http/controller/api"
7 | "go-app/app/http/middleware"
8 | "go-app/lib/response"
9 | "net/http"
10 |
11 | "github.com/gin-gonic/gin"
12 | )
13 |
14 | func RegisterApiRouter(r *gin.Engine) {
15 |
16 | r.StaticFS("/upload", http.Dir("./upload"))
17 |
18 | v1 := r.Group("/v1")
19 | {
20 | // 无需身份认证的接口
21 | routerNoneAuth(v1)
22 |
23 | // 需身份认证的接口
24 | v1.Use(middleware.JWTAuth())
25 | {
26 | v1.GET("/get_routers", response.JSON(api.MenuController.Router))
27 |
28 | // 需要casbin权限认证
29 | v1.Use(middleware.CasbinCheck("/v1"))
30 | {
31 | routerSystemSetting(v1)
32 | }
33 | }
34 | }
35 |
36 | r.NoRoute(func(c *gin.Context) {
37 | // 实现内部重定向
38 | c.JSON(http.StatusNotFound, response.NewJson().Error(code.ErrRoute))
39 | })
40 | }
41 |
42 | // 无需身份认证的接口
43 | func routerNoneAuth(r *gin.RouterGroup) {
44 | // 测试接口
45 | r.GET("/", response.JSON(api.TestController.Ip))
46 | r.POST("/login", binding.User.Login(), binding.User.VerifyTurnstile(), response.JSON(api.AuthController.Login))
47 | r.POST("/refreshToken", binding.User.RefreshToken(), response.JSON(api.AuthController.RefreshToken))
48 | }
49 |
50 | // 系统管理路由
51 | func routerSystemSetting(r *gin.RouterGroup) {
52 | // 菜单管理
53 | sysMenu := r.Group("/sys/menus")
54 | {
55 | sysMenu.GET("/list", response.JSON(api.MenuController.List))
56 | sysMenu.POST("/edit", binding.Menu.Edit(), response.JSON(api.MenuController.Edit))
57 | sysMenu.POST("/del", binding.Common.ID(), response.JSON(api.MenuController.Del))
58 | }
59 |
60 | // 角色管理
61 | sysRole := r.Group("/sys/roles")
62 | {
63 | sysRole.GET("/list", response.JSON(api.RoleController.List))
64 | sysRole.POST("/edit", binding.Role.Edit(), response.JSON(api.RoleController.Edit))
65 | sysRole.POST("/del", binding.Common.ID(), response.JSON(api.RoleController.Del))
66 | sysRole.GET("/permission", binding.Common.ID(), response.JSON(api.RoleController.Permission))
67 | sysRole.POST("/assign", binding.Role.Assign(), response.JSON(api.RoleController.Assign))
68 | }
69 |
70 | // 用户管理
71 | sysUser := r.Group("/sys/users")
72 | {
73 | sysUser.GET("/list", binding.Common.Keyword(), binding.Common.Pager(), response.JSON(api.UsersController.List))
74 | sysUser.POST("/edit", binding.Users.Edit(), response.JSON(api.UsersController.Edit))
75 | sysUser.POST("/del", binding.Common.ID(), response.JSON(api.UsersController.Del))
76 | sysUser.POST("/assign", binding.Users.Assign(), response.JSON(api.UsersController.Assign))
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/router/web.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "go-app/app/http/controller/web"
5 | "go-app/lib/response"
6 | "net/http"
7 |
8 | "github.com/gin-gonic/gin"
9 | )
10 |
11 | func RegisterWebRouter(r *gin.Engine) {
12 |
13 | r.GET("/favicon.ico", func(c *gin.Context) {
14 | c.AbortWithStatus(http.StatusNotFound)
15 | })
16 |
17 | TestController := new(web.TestController)
18 | r.GET("/", response.HTML(TestController.Ip))
19 |
20 | r.NoRoute(func(c *gin.Context) {
21 | // 实现内部重定向
22 | c.Redirect(http.StatusTemporaryRedirect, "/")
23 | })
24 | }
25 |
--------------------------------------------------------------------------------