├── .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](https://img.shields.io/github/license/anerg2046/go-admin-server.svg)](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 | --------------------------------------------------------------------------------