feat: initialize project

This commit is contained in:
chris 2025-03-24 13:55:56 +08:00
parent 4b6c7d0496
commit e55167549a
424 changed files with 115547 additions and 0 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
.git
.idea

14
.gitattributes vendored Normal file
View File

@ -0,0 +1,14 @@
* text=auto
# Force the following filetypes to have unix eols, so Windows does not break them
*.* text eol=lf
# Windows forced line-endings
/.idea/* text eol=crlf
#
## These files are binary and should be left untouched
#
# (binary is a macro for -text -diff)
*.png binary

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
storage/logs
.idea
*.log
deploy/docker-compose/conf
deploy/docker-compose/data

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Nunu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

36
Makefile Normal file
View File

@ -0,0 +1,36 @@
.PHONY: init
init:
go install github.com/google/wire/cmd/wire@latest
go install github.com/golang/mock/mockgen@latest
go install github.com/swaggo/swag/cmd/swag@latest
.PHONY: bootstrap
bootstrap:
cd ./deploy/docker-compose && docker compose up -d && cd ../../
go run ./cmd/migration
nunu run ./cmd/server
.PHONY: mock
mock:
mockgen -source=internal/service/user.go -destination test/mocks/service/user.go
mockgen -source=internal/repository/user.go -destination test/mocks/repository/user.go
mockgen -source=internal/repository/repository.go -destination test/mocks/repository/repository.go
.PHONY: test
test:
go test -coverpkg=./internal/handler,./internal/service,./internal/repository -coverprofile=./coverage.out ./test/server/...
go tool cover -html=./coverage.out -o coverage.html
.PHONY: build
build:
go build -ldflags="-s -w" -o ./bin/server ./cmd/server
cd web && npm run build
.PHONY: docker
docker:
docker build -f deploy/build/Dockerfile --build-arg APP_RELATIVE_PATH=./cmd/task -t 1.1.1.1:5000/demo-task:v1 .
docker run --rm -i 1.1.1.1:5000/demo-task:v1
.PHONY: swag
swag:
swag init -g cmd/server/main.go -o ./docs --parseDependency

228
api/v1/admin.go Normal file
View File

@ -0,0 +1,228 @@
package v1
type LoginRequest struct {
Username string `json:"username" binding:"required" example:"1234@gmail.com"`
Password string `json:"password" binding:"required" example:"123456"`
}
type LoginResponseData struct {
AccessToken string `json:"accessToken"`
}
type LoginResponse struct {
Response
Data LoginResponseData
}
type AdminUserDataItem struct {
ID uint `json:"id"`
Username string `json:"username" binding:"required" example:"张三"`
Nickname string `json:"nickname" binding:"required" example:"小Baby"`
Password string `json:"password" binding:"required" example:"123456"`
Email string `json:"email" binding:"required,email" example:"1234@gmail.com"`
Phone string `form:"phone" binding:"" example:"1858888888"`
Roles []string `json:"roles" example:""`
UpdatedAt string `json:"updatedAt"`
CreatedAt string `json:"createdAt"`
}
type GetAdminUsersRequest struct {
Page int `form:"page" binding:"required" example:"1"`
PageSize int `form:"pageSize" binding:"required" example:"10"`
Username string `json:"username" binding:"" example:"张三"`
Nickname string `json:"nickname" binding:"" example:"小Baby"`
Phone string `form:"phone" binding:"" example:"1858888888"`
Email string `form:"email" binding:"" example:"1234@gmail.com"`
}
type GetAdminUserResponseData struct {
ID uint `json:"id"`
Username string `json:"username" example:"张三"`
Nickname string `json:"nickname" example:"小Baby"`
Password string `json:"password" example:"123456"`
Email string `json:"email" example:"1234@gmail.com"`
Phone string `form:"phone" example:"1858888888"`
Roles []string `json:"roles" example:""`
UpdatedAt string `json:"updatedAt"`
CreatedAt string `json:"createdAt"`
}
type GetAdminUserResponse struct {
Response
Data GetAdminUserResponseData
}
type GetAdminUsersResponseData struct {
List []AdminUserDataItem `json:"list"`
Total int64 `json:"total"`
}
type GetAdminUsersResponse struct {
Response
Data GetAdminUsersResponseData
}
type AdminUserCreateRequest struct {
Username string `json:"username" binding:"required" example:"张三"`
Nickname string `json:"nickname" binding:"" example:"小Baby"`
Password string `json:"password" binding:"required" example:"123456"`
Email string `json:"email" binding:"" example:"1234@gmail.com"`
Phone string `form:"phone" binding:"" example:"1858888888"`
Roles []string `json:"roles" example:""`
}
type AdminUserUpdateRequest struct {
ID uint `json:"id"`
Username string `json:"username" binding:"required" example:"张三"`
Nickname string `json:"nickname" binding:"" example:"小Baby"`
Password string `json:"password" binding:"" example:"123456"`
Email string `json:"email" binding:"" example:"1234@gmail.com"`
Phone string `form:"phone" binding:"" example:"1858888888"`
Roles []string `json:"roles" example:""`
}
type AdminUserDeleteRequest struct {
ID uint `form:"id" binding:"required" example:"1"`
}
type MenuDataItem struct {
ID uint `json:"id,omitempty"` // 唯一id使用整数表示
ParentID uint `json:"parentId,omitempty"` // 父级菜单的id使用整数表示
Weight int `json:"weight"` // 排序权重
Path string `json:"path"` // 地址
Title string `json:"title"` // 展示名称
Name string `json:"name,omitempty"` // 同路由中的name唯一标识
Component string `json:"component,omitempty"` // 绑定的组件
Locale string `json:"locale,omitempty"` // 本地化标识
Icon string `json:"icon,omitempty"` // 图标,使用字符串表示
Redirect string `json:"redirect,omitempty"` // 重定向地址
KeepAlive bool `json:"keepAlive,omitempty"` // 是否保活
HideInMenu bool `json:"hideInMenu,omitempty"` // 是否保活
URL string `json:"url,omitempty"` // iframe模式下的跳转url不能与path重复
UpdatedAt string `json:"updatedAt,omitempty"` // 是否保活
}
type GetMenuResponseData struct {
List []MenuDataItem `json:"list"`
}
type GetMenuResponse struct {
Response
Data GetMenuResponseData
}
type MenuCreateRequest struct {
ParentID uint `json:"parentId,omitempty"` // 父级菜单的id使用整数表示
Weight int `json:"weight"` // 排序权重
Path string `json:"path"` // 地址
Title string `json:"title"` // 展示名称
Name string `json:"name,omitempty"` // 同路由中的name唯一标识
Component string `json:"component,omitempty"` // 绑定的组件
Locale string `json:"locale,omitempty"` // 本地化标识
Icon string `json:"icon,omitempty"` // 图标,使用字符串表示
Redirect string `json:"redirect,omitempty"` // 重定向地址
KeepAlive bool `json:"keepAlive,omitempty"` // 是否保活
HideInMenu bool `json:"hideInMenu,omitempty"` // 是否保活
URL string `json:"url,omitempty"` // iframe模式下的跳转url不能与path重复
}
type MenuUpdateRequest struct {
ID uint `json:"id,omitempty"` // 唯一id使用整数表示
ParentID uint `json:"parentId,omitempty"` // 父级菜单的id使用整数表示
Weight int `json:"weight"` // 排序权重
Path string `json:"path"` // 地址
Title string `json:"title"` // 展示名称
Name string `json:"name,omitempty"` // 同路由中的name唯一标识
Component string `json:"component,omitempty"` // 绑定的组件
Locale string `json:"locale,omitempty"` // 本地化标识
Icon string `json:"icon,omitempty"` // 图标,使用字符串表示
Redirect string `json:"redirect,omitempty"` // 重定向地址
KeepAlive bool `json:"keepAlive,omitempty"` // 是否保活
HideInMenu bool `json:"hideInMenu,omitempty"` // 是否保活
URL string `json:"url,omitempty"` // iframe模式下的跳转url不能与path重复
UpdatedAt string `json:"updatedAt"`
}
type MenuDeleteRequest struct {
ID uint `form:"id"` // 唯一id使用整数表示
}
type GetRoleListRequest struct {
Page int `form:"page" binding:"required" example:"1"`
PageSize int `form:"pageSize" binding:"required" example:"10"`
Sid string `form:"sid" binding:"" example:"1"`
Name string `form:"name" binding:"" example:"Admin"`
}
type RoleDataItem struct {
ID uint `json:"id"`
Name string `json:"name"`
Sid string `json:"sid"`
UpdatedAt string `json:"updatedAt"`
CreatedAt string `json:"createdAt"`
}
type GetRolesResponseData struct {
List []RoleDataItem `json:"list"`
Total int64 `json:"total"`
}
type GetRolesResponse struct {
Response
Data GetRolesResponseData
}
type RoleCreateRequest struct {
Sid string `form:"sid" binding:"required" example:"1"`
Name string `form:"name" binding:"required" example:"Admin"`
}
type RoleUpdateRequest struct {
ID uint `form:"id" binding:"required" example:"1"`
Sid string `form:"sid" binding:"required" example:"1"`
Name string `form:"name" binding:"required" example:"Admin"`
}
type RoleDeleteRequest struct {
ID uint `form:"id" binding:"required" example:"1"`
}
type PermissionCreateRequest struct {
Sid string `form:"sid" binding:"required" example:"1"`
Name string `form:"name" binding:"required" example:"Admin"`
}
type GetApisRequest struct {
Page int `form:"page" binding:"required" example:"1"`
PageSize int `form:"pageSize" binding:"required" example:"10"`
Group string `form:"group" binding:"" example:"权限管理"`
Name string `form:"name" binding:"" example:"菜单列表"`
Path string `form:"path" binding:"" example:"/v1/test"`
Method string `form:"method" binding:"" example:"GET"`
}
type ApiDataItem struct {
ID uint `json:"id"`
Name string `json:"name"`
Path string `json:"path"`
Method string `json:"method"`
Group string `json:"group"`
UpdatedAt string `json:"updatedAt"`
CreatedAt string `json:"createdAt"`
}
type GetApisResponseData struct {
List []ApiDataItem `json:"list"`
Total int64 `json:"total"`
Groups []string `json:"groups"`
}
type GetApisResponse struct {
Response
Data GetApisResponseData
}
type ApiCreateRequest struct {
Group string `form:"group" binding:"" example:"权限管理"`
Name string `form:"name" binding:"" example:"菜单列表"`
Path string `form:"path" binding:"" example:"/v1/test"`
Method string `form:"method" binding:"" example:"GET"`
}
type ApiUpdateRequest struct {
ID uint `form:"id" binding:"required" example:"1"`
Group string `form:"group" binding:"" example:"权限管理"`
Name string `form:"name" binding:"" example:"菜单列表"`
Path string `form:"path" binding:"" example:"/v1/test"`
Method string `form:"method" binding:"" example:"GET"`
}
type ApiDeleteRequest struct {
ID uint `form:"id" binding:"required" example:"1"`
}
type GetUserPermissionsData struct {
List []string `json:"list"`
}
type GetRolePermissionsRequest struct {
Role string `form:"role" binding:"required" example:"admin"`
}
type GetRolePermissionsData struct {
List []string `json:"list"`
}
type UpdateRolePermissionRequest struct {
Role string `form:"role" binding:"required" example:"admin"`
List []string `form:"list" binding:"required" example:""`
}

14
api/v1/errors.go Normal file
View File

@ -0,0 +1,14 @@
package v1
var (
// common errors
ErrSuccess = newError(0, "ok")
ErrBadRequest = newError(400, "参数错误")
ErrUnauthorized = newError(401, "登录失效,请重新登录~")
ErrNotFound = newError(404, "数据不存在")
ErrForbidden = newError(403, "权限不足,请联系管理员开通权限~")
ErrInternalServerError = newError(500, "服务器错误~")
// more biz errors
ErrUsernameAlreadyUse = newError(1001, "The username is already in use.")
)

51
api/v1/v1.go Normal file
View File

@ -0,0 +1,51 @@
package v1
import (
"errors"
"github.com/gin-gonic/gin"
"net/http"
)
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
func HandleSuccess(ctx *gin.Context, data interface{}) {
if data == nil {
data = map[string]interface{}{}
}
resp := Response{Code: errorCodeMap[ErrSuccess], Message: ErrSuccess.Error(), Data: data}
if _, ok := errorCodeMap[ErrSuccess]; !ok {
resp = Response{Code: 0, Message: "", Data: data}
}
ctx.JSON(http.StatusOK, resp)
}
func HandleError(ctx *gin.Context, httpCode int, err error, data interface{}) {
if data == nil {
data = map[string]string{}
}
resp := Response{Code: errorCodeMap[err], Message: err.Error(), Data: data}
if _, ok := errorCodeMap[err]; !ok {
resp = Response{Code: 500, Message: "unknown error", Data: data}
}
ctx.JSON(httpCode, resp)
}
type Error struct {
Code int
Message string
}
var errorCodeMap = map[error]int{}
func newError(code int, msg string) error {
err := errors.New(msg)
errorCodeMap[err] = code
return err
}
func (e Error) Error() string {
return e.Message
}

26
cmd/migration/main.go Normal file
View File

@ -0,0 +1,26 @@
package main
import (
"context"
"flag"
"nunu-layout-admin/cmd/migration/wire"
"nunu-layout-admin/pkg/config"
"nunu-layout-admin/pkg/log"
)
func main() {
var envConf = flag.String("conf", "config/local.yml", "config path, eg: -conf ./config/local.yml")
flag.Parse()
conf := config.NewConfig(*envConf)
logger := log.NewLog(conf)
app, cleanup, err := wire.NewWire(conf, logger)
defer cleanup()
if err != nil {
panic(err)
}
if err = app.Run(context.Background()); err != nil {
panic(err)
}
}

View File

@ -0,0 +1,43 @@
//go:build wireinject
// +build wireinject
package wire
import (
"github.com/google/wire"
"github.com/spf13/viper"
"nunu-layout-admin/internal/repository"
"nunu-layout-admin/internal/server"
"nunu-layout-admin/pkg/app"
"nunu-layout-admin/pkg/log"
"nunu-layout-admin/pkg/sid"
)
var repositorySet = wire.NewSet(
repository.NewDB,
//repository.NewRedis,
repository.NewRepository,
repository.NewCasbinEnforcer,
)
var serverSet = wire.NewSet(
server.NewMigrateServer,
)
// build App
func newApp(
migrateServer *server.MigrateServer,
) *app.App {
return app.NewApp(
app.WithServer(migrateServer),
app.WithName("demo-migrate"),
)
}
func NewWire(*viper.Viper, *log.Logger) (*app.App, func(), error) {
panic(wire.Build(
repositorySet,
serverSet,
sid.NewSid,
newApp,
))
}

View File

@ -0,0 +1,42 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package wire
import (
"github.com/google/wire"
"github.com/spf13/viper"
"nunu-layout-admin/internal/repository"
"nunu-layout-admin/internal/server"
"nunu-layout-admin/pkg/app"
"nunu-layout-admin/pkg/log"
"nunu-layout-admin/pkg/sid"
)
// Injectors from wire.go:
func NewWire(viperViper *viper.Viper, logger *log.Logger) (*app.App, func(), error) {
db := repository.NewDB(viperViper, logger)
sidSid := sid.NewSid()
syncedEnforcer := repository.NewCasbinEnforcer(viperViper, logger, db)
migrateServer := server.NewMigrateServer(db, logger, sidSid, syncedEnforcer)
appApp := newApp(migrateServer)
return appApp, func() {
}, nil
}
// wire.go:
var repositorySet = wire.NewSet(repository.NewDB, repository.NewRepository, repository.NewCasbinEnforcer)
var serverSet = wire.NewSet(server.NewMigrateServer)
// build App
func newApp(
migrateServer *server.MigrateServer,
) *app.App {
return app.NewApp(app.WithServer(migrateServer), app.WithName("demo-migrate"))
}

46
cmd/server/main.go Normal file
View File

@ -0,0 +1,46 @@
package main
import (
"context"
"flag"
"fmt"
"nunu-layout-admin/pkg/log"
"go.uber.org/zap"
"nunu-layout-admin/cmd/server/wire"
"nunu-layout-admin/pkg/config"
)
// @title Nunu Example API
// @version 1.0.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8000
// @securityDefinitions.apiKey Bearer
// @in header
// @name Authorization
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api/
func main() {
var envConf = flag.String("conf", "config/local.yml", "config path, eg: -conf ./config/local.yml")
flag.Parse()
conf := config.NewConfig(*envConf)
logger := log.NewLog(conf)
app, cleanup, err := wire.NewWire(conf, logger)
defer cleanup()
if err != nil {
panic(err)
}
logger.Info("server start", zap.String("host", fmt.Sprintf("http://%s:%d", conf.GetString("http.host"), conf.GetInt("http.port"))))
logger.Info("docs addr", zap.String("addr", fmt.Sprintf("http://%s:%d/swagger/index.html", conf.GetString("http.host"), conf.GetInt("http.port"))))
if err = app.Run(context.Background()); err != nil {
panic(err)
}
}

75
cmd/server/wire/wire.go Normal file
View File

@ -0,0 +1,75 @@
//go:build wireinject
// +build wireinject
package wire
import (
"github.com/google/wire"
"github.com/spf13/viper"
"nunu-layout-admin/internal/handler"
"nunu-layout-admin/internal/job"
"nunu-layout-admin/internal/repository"
"nunu-layout-admin/internal/server"
"nunu-layout-admin/internal/service"
"nunu-layout-admin/pkg/app"
"nunu-layout-admin/pkg/jwt"
"nunu-layout-admin/pkg/log"
"nunu-layout-admin/pkg/server/http"
"nunu-layout-admin/pkg/sid"
)
var repositorySet = wire.NewSet(
repository.NewDB,
//repository.NewRedis,
repository.NewRepository,
repository.NewTransaction,
repository.NewUserRepository,
repository.NewCasbinEnforcer,
repository.NewAdminRepository,
)
var serviceSet = wire.NewSet(
service.NewService,
service.NewUserService,
service.NewAdminService,
)
var handlerSet = wire.NewSet(
handler.NewHandler,
handler.NewUserHandler,
handler.NewAdminHandler,
)
var jobSet = wire.NewSet(
job.NewJob,
job.NewUserJob,
)
var serverSet = wire.NewSet(
server.NewHTTPServer,
server.NewJobServer,
)
// build App
func newApp(
httpServer *http.Server,
jobServer *server.JobServer,
// task *server.Task,
) *app.App {
return app.NewApp(
app.WithServer(httpServer, jobServer),
app.WithName("demo-server"),
)
}
func NewWire(*viper.Viper, *log.Logger) (*app.App, func(), error) {
panic(wire.Build(
repositorySet,
serviceSet,
handlerSet,
jobSet,
serverSet,
sid.NewSid,
jwt.NewJwt,
newApp,
))
}

View File

@ -0,0 +1,69 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package wire
import (
"github.com/google/wire"
"github.com/spf13/viper"
"nunu-layout-admin/internal/handler"
"nunu-layout-admin/internal/job"
"nunu-layout-admin/internal/repository"
"nunu-layout-admin/internal/server"
"nunu-layout-admin/internal/service"
"nunu-layout-admin/pkg/app"
"nunu-layout-admin/pkg/jwt"
"nunu-layout-admin/pkg/log"
"nunu-layout-admin/pkg/server/http"
"nunu-layout-admin/pkg/sid"
)
// Injectors from wire.go:
func NewWire(viperViper *viper.Viper, logger *log.Logger) (*app.App, func(), error) {
jwtJWT := jwt.NewJwt(viperViper)
db := repository.NewDB(viperViper, logger)
syncedEnforcer := repository.NewCasbinEnforcer(viperViper, logger, db)
handlerHandler := handler.NewHandler(logger)
repositoryRepository := repository.NewRepository(logger, db, syncedEnforcer)
transaction := repository.NewTransaction(repositoryRepository)
sidSid := sid.NewSid()
serviceService := service.NewService(transaction, logger, sidSid, jwtJWT)
adminRepository := repository.NewAdminRepository(repositoryRepository)
adminService := service.NewAdminService(serviceService, adminRepository)
adminHandler := handler.NewAdminHandler(handlerHandler, adminService)
userRepository := repository.NewUserRepository(repositoryRepository)
userService := service.NewUserService(serviceService, userRepository)
userHandler := handler.NewUserHandler(handlerHandler, userService)
httpServer := server.NewHTTPServer(logger, viperViper, jwtJWT, syncedEnforcer, adminHandler, userHandler)
jobJob := job.NewJob(transaction, logger, sidSid)
userJob := job.NewUserJob(jobJob, userRepository)
jobServer := server.NewJobServer(logger, userJob)
appApp := newApp(httpServer, jobServer)
return appApp, func() {
}, nil
}
// wire.go:
var repositorySet = wire.NewSet(repository.NewDB, repository.NewRepository, repository.NewTransaction, repository.NewUserRepository, repository.NewCasbinEnforcer, repository.NewAdminRepository)
var serviceSet = wire.NewSet(service.NewService, service.NewUserService, service.NewAdminService)
var handlerSet = wire.NewSet(handler.NewHandler, handler.NewUserHandler, handler.NewAdminHandler)
var jobSet = wire.NewSet(job.NewJob, job.NewUserJob)
var serverSet = wire.NewSet(server.NewHTTPServer, server.NewJobServer)
// build App
func newApp(
httpServer *http.Server,
jobServer *server.JobServer,
) *app.App {
return app.NewApp(app.WithServer(httpServer, jobServer), app.WithName("demo-server"))
}

27
cmd/task/main.go Normal file
View File

@ -0,0 +1,27 @@
package main
import (
"context"
"flag"
"nunu-layout-admin/cmd/task/wire"
"nunu-layout-admin/pkg/config"
"nunu-layout-admin/pkg/log"
)
func main() {
var envConf = flag.String("conf", "config/local.yml", "config path, eg: -conf ./config/local.yml")
flag.Parse()
conf := config.NewConfig(*envConf)
logger := log.NewLog(conf)
logger.Info("start task")
app, cleanup, err := wire.NewWire(conf, logger)
defer cleanup()
if err != nil {
panic(err)
}
if err = app.Run(context.Background()); err != nil {
panic(err)
}
}

52
cmd/task/wire/wire.go Normal file
View File

@ -0,0 +1,52 @@
//go:build wireinject
// +build wireinject
package wire
import (
"github.com/google/wire"
"github.com/spf13/viper"
"nunu-layout-admin/internal/repository"
"nunu-layout-admin/internal/server"
"nunu-layout-admin/internal/task"
"nunu-layout-admin/pkg/app"
"nunu-layout-admin/pkg/log"
"nunu-layout-admin/pkg/sid"
)
var repositorySet = wire.NewSet(
repository.NewDB,
//repository.NewRedis,
repository.NewRepository,
repository.NewTransaction,
repository.NewUserRepository,
repository.NewCasbinEnforcer,
)
var taskSet = wire.NewSet(
task.NewTask,
task.NewUserTask,
)
var serverSet = wire.NewSet(
server.NewTaskServer,
)
// build App
func newApp(
task *server.TaskServer,
) *app.App {
return app.NewApp(
app.WithServer(task),
app.WithName("demo-task"),
)
}
func NewWire(*viper.Viper, *log.Logger) (*app.App, func(), error) {
panic(wire.Build(
repositorySet,
taskSet,
serverSet,
newApp,
sid.NewSid,
))
}

49
cmd/task/wire/wire_gen.go Normal file
View File

@ -0,0 +1,49 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package wire
import (
"github.com/google/wire"
"github.com/spf13/viper"
"nunu-layout-admin/internal/repository"
"nunu-layout-admin/internal/server"
"nunu-layout-admin/internal/task"
"nunu-layout-admin/pkg/app"
"nunu-layout-admin/pkg/log"
"nunu-layout-admin/pkg/sid"
)
// Injectors from wire.go:
func NewWire(viperViper *viper.Viper, logger *log.Logger) (*app.App, func(), error) {
db := repository.NewDB(viperViper, logger)
syncedEnforcer := repository.NewCasbinEnforcer(viperViper, logger, db)
repositoryRepository := repository.NewRepository(logger, db, syncedEnforcer)
transaction := repository.NewTransaction(repositoryRepository)
sidSid := sid.NewSid()
taskTask := task.NewTask(transaction, logger, sidSid)
userRepository := repository.NewUserRepository(repositoryRepository)
userTask := task.NewUserTask(taskTask, userRepository)
taskServer := server.NewTaskServer(logger, userTask)
appApp := newApp(taskServer)
return appApp, func() {
}, nil
}
// wire.go:
var repositorySet = wire.NewSet(repository.NewDB, repository.NewRepository, repository.NewTransaction, repository.NewUserRepository, repository.NewCasbinEnforcer)
var taskSet = wire.NewSet(task.NewTask, task.NewUserTask)
var serverSet = wire.NewSet(server.NewTaskServer)
// build App
func newApp(task2 *server.TaskServer,
) *app.App {
return app.NewApp(app.WithServer(task2), app.WithName("demo-task"))
}

37
config/local.yml Normal file
View File

@ -0,0 +1,37 @@
env: local
http:
# host: 0.0.0.0
host: 127.0.0.1
port: 8000
security:
api_sign:
app_key: 123456
app_security: 123456
jwt:
key: QQYnRFerJTSEcrfB89fw8prOaObmrch8
data:
db:
user:
driver: sqlite
dsn: storage/nunu-test.db?_busy_timeout=5000
# user:
# driver: mysql
# dsn: root:123456@tcp(127.0.0.1:3380)/user?charset=utf8mb4&parseTime=True&loc=Local
# user:
# driver: postgres
# dsn: host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai
redis:
addr: 127.0.0.1:6350
password: ""
db: 0
read_timeout: 0.2s
write_timeout: 0.2s
log:
log_level: debug
encoding: console # json or console
log_file_name: "./storage/logs/server.log"
max_backups: 30
max_age: 7
max_size: 1024
compress: true

37
config/prod.yml Normal file
View File

@ -0,0 +1,37 @@
env: prod
http:
host: 0.0.0.0
# host: 127.0.0.1
port: 8000
security:
api_sign:
app_key: 123456
app_security: 123456
jwt:
key: QQYnRFerJTSEcrfB89fw8prOaObmrch8
data:
db:
user:
driver: sqlite
dsn: storage/nunu-test.db?_busy_timeout=5000
# user:
# driver: mysql
# dsn: root:123456@tcp(127.0.0.1:3380)/user?charset=utf8mb4&parseTime=True&loc=Local
# user:
# driver: postgres
# dsn: host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai
redis:
addr: 127.0.0.1:6350
password: ""
db: 0
read_timeout: 0.2s
write_timeout: 0.2s
log:
log_level: info
encoding: json # json or console
log_file_name: "./storage/logs/server.log"
max_backups: 30
max_age: 7
max_size: 1024
compress: true

34
deploy/build/Dockerfile Normal file
View File

@ -0,0 +1,34 @@
ARG REGISTRY=docker.io
FROM ${REGISTRY}/golang:1.19-alpine AS builder
RUN set -eux && sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
ARG APP_RELATIVE_PATH
COPY .. /data/app
WORKDIR /data/app
RUN rm -rf /data/app/bin/
RUN export GOPROXY=https://goproxy.cn,direct && go mod tidy && go build -ldflags="-s -w" -o ./bin/server ${APP_RELATIVE_PATH}
RUN mv config /data/app/bin/
FROM ${REGISTRY}/alpine:3.16
RUN set -eux && sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& apk del tzdata
ARG APP_ENV
ENV APP_ENV=${APP_ENV}
WORKDIR /data/app
COPY --from=builder /data/app/bin /data/app
EXPOSE 8000
ENTRYPOINT [ "./server" ]
#docker build -t 1.1.1.1:5000/demo-api:v1 --build-arg APP_CONF=config/prod.yml --build-arg APP_RELATIVE_PATH=./cmd/server/... .
#docker run -it --rm --entrypoint=ash 1.1.1.1:5000/demo-api:v1

View File

@ -0,0 +1,25 @@
version: '3'
services:
user-db:
image: mysql:8.0.31-debian
hostname: user-db
container_name: user-db
ports:
- 3380:3306
environment:
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_ROOT_HOST=%
- MYSQL_DATABASE=user
# volumes:
# - ./data/mysql/user:/var/lib/mysql
# - ./conf/mysql/conf.d:/etc/mysql/conf.d
cache-redis:
image: redis:6-alpine
hostname: cache-redis
# volumes:
# - ./data/redis/cache/:/data
# - ./conf/redis/cache/redis.conf:/etc/redis/redis.conf
ports:
- 6350:6379
command: ["redis-server","/etc/redis/redis.conf"]

1625
docs/docs.go Normal file

File diff suppressed because it is too large Load Diff

1584
docs/swagger.json Normal file

File diff suppressed because it is too large Load Diff

1041
docs/swagger.yaml Normal file

File diff suppressed because it is too large Load Diff

105
go.mod Normal file
View File

@ -0,0 +1,105 @@
module nunu-layout-admin
go 1.23.0
toolchain go1.24.0
require (
github.com/casbin/casbin/v2 v2.104.0
github.com/casbin/gorm-adapter/v3 v3.32.0
github.com/duke-git/lancet/v2 v2.3.0
github.com/gin-contrib/static v1.1.3
github.com/gin-gonic/gin v1.10.0
github.com/glebarez/sqlite v1.11.0
github.com/go-co-op/gocron v1.28.2
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/google/wire v0.5.0
github.com/redis/go-redis/v9 v9.0.5
github.com/sony/sonyflake v1.1.0
github.com/spf13/viper v1.16.0
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.4
go.uber.org/zap v1.26.0
golang.org/x/crypto v0.36.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.5.9
gorm.io/gorm v1.25.12
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
github.com/bytedance/sonic v1.13.2 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/casbin/govaluate v1.3.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.25.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/microsoft/go-mssqldb v1.6.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.15.0 // indirect
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.31.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/sqlserver v1.5.3 // indirect
gorm.io/plugin/dbresolver v1.5.3 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
)

829
go.sum Normal file
View File

@ -0,0 +1,829 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 h1:/iHxaJhsFr0+xVFfbMr5vxz848jyiWuIEDhYq3y5odY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 h1:yfJe15aSwEQ6Oo6J+gdfdulPNoZ3TEhmbhLIoxZcA+U=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028gtTPiYt/RMUfs8nVsAL7FDQrfLlrm/NnRG/zcC4=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0 h1:HCc0+LpPfpCKs6LGGLAhwBARt9632unrVcI6i8s/8os=
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk=
github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/casbin/casbin/v2 v2.104.0 h1:qDakyBZ4jUg1VskF1+UzIwkg+uXWcp0u0M9PMm1RsTA=
github.com/casbin/casbin/v2 v2.104.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco=
github.com/casbin/gorm-adapter/v3 v3.32.0 h1:Au+IOILBIE9clox5BJhI2nA3p9t7Ep1ePlupdGbGfus=
github.com/casbin/gorm-adapter/v3 v3.32.0/go.mod h1:Zre/H8p17mpv5U3EaWgPoxLILLdXO3gHW5aoQQpUDZI=
github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/duke-git/lancet/v2 v2.3.0 h1:Ztie0qOnC4QgGYYqmpmQxbxkPcm54kqFXj1bwhiV8zg=
github.com/duke-git/lancet/v2 v2.3.0/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-contrib/static v1.1.3 h1:WLOpkBtMDJ3gATFZgNJyVibFMio/UHonnueqJsQ0w4U=
github.com/gin-contrib/static v1.1.3/go.mod h1:zejpJ/YWp8cZj/6EpiL5f/+skv5daQTNwRx1E8Pci30=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-co-op/gocron v1.28.2 h1:H9oHUGH+9HZ5mAorbnzRjzXLf4poP+ctZdbtaKRYagc=
github.com/go-co-op/gocron v1.28.2/go.mod h1:39f6KNSGVOU1LO/ZOoZfcSxwlsJDQOKSu8erN0SH48Y=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/microsoft/go-mssqldb v1.6.0 h1:mM3gYdVwEPFrlg/Dvr2DNVEgYFG7L42l+dGc67NNNpc=
github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o=
github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sony/sonyflake v1.1.0 h1:wnrEcL3aOkWmPlhScLEGAXKkLAIslnBteNUq4Bw6MM4=
github.com/sony/sonyflake v1.1.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04=
github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a h1:4iLhBPcpqFmylhnkbY3W0ONLUYYkDAW9xMFLfxgsvCw=
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/sqlserver v1.5.3 h1:rjupPS4PVw+rjJkfvr8jn2lJ8BMhT4UW5FwuJY0P3Z0=
gorm.io/driver/sqlserver v1.5.3/go.mod h1:B+CZ0/7oFJ6tAlefsKoyxdgDCXJKSgwS2bMOQZT0I00=
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
gorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU=
gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

561
internal/handler/admin.go Normal file
View File

@ -0,0 +1,561 @@
package handler
import (
"github.com/gin-gonic/gin"
"net/http"
v1 "nunu-layout-admin/api/v1"
"nunu-layout-admin/internal/service"
)
type AdminHandler struct {
*Handler
adminService service.AdminService
}
func NewAdminHandler(
handler *Handler,
adminService service.AdminService,
) *AdminHandler {
return &AdminHandler{
Handler: handler,
adminService: adminService,
}
}
// Login godoc
// @Summary 账号登录
// @Schemes
// @Description
// @Tags 用户模块
// @Accept json
// @Produce json
// @Param request body v1.LoginRequest true "params"
// @Success 200 {object} v1.LoginResponse
// @Router /v1/login [post]
func (h *AdminHandler) Login(ctx *gin.Context) {
var req v1.LoginRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
token, err := h.adminService.Login(ctx, &req)
if err != nil {
v1.HandleError(ctx, http.StatusUnauthorized, v1.ErrUnauthorized, nil)
return
}
v1.HandleSuccess(ctx, v1.LoginResponseData{
AccessToken: token,
})
}
// GetMenus godoc
// @Summary 获取用户菜单
// @Schemes
// @Description 获取当前用户的菜单列表
// @Tags 菜单模块
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} v1.GetMenuResponse
// @Router /v1/menus [get]
func (h *AdminHandler) GetMenus(ctx *gin.Context) {
data, err := h.adminService.GetMenus(ctx, GetUserIdFromCtx(ctx))
if err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
// 过滤权限菜单
v1.HandleSuccess(ctx, data)
}
// GetAdminMenus godoc
// @Summary 获取管理员菜单
// @Schemes
// @Description 获取管理员菜单列表
// @Tags 菜单模块
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} v1.GetMenuResponse
// @Router /v1/admin/menus [get]
func (h *AdminHandler) GetAdminMenus(ctx *gin.Context) {
data, err := h.adminService.GetAdminMenus(ctx)
if err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
// 过滤权限菜单
v1.HandleSuccess(ctx, data)
}
// GetUserPermissions godoc
// @Summary 获取用户权限
// @Schemes
// @Description 获取当前用户的权限列表
// @Tags 权限模块
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} v1.GetUserPermissionsData
// @Router /v1/admin/user/permissions [get]
func (h *AdminHandler) GetUserPermissions(ctx *gin.Context) {
data, err := h.adminService.GetUserPermissions(ctx, GetUserIdFromCtx(ctx))
if err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
// 过滤权限菜单
v1.HandleSuccess(ctx, data)
}
// GetRolePermissions godoc
// @Summary 获取角色权限
// @Schemes
// @Description 获取指定角色的权限列表
// @Tags 权限模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param role query string true "角色名称"
// @Success 200 {object} v1.GetRolePermissionsData
// @Router /v1/admin/role/permissions [get]
func (h *AdminHandler) GetRolePermissions(ctx *gin.Context) {
var req v1.GetRolePermissionsRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
data, err := h.adminService.GetRolePermissions(ctx, req.Role)
if err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, data)
}
// UpdateRolePermission godoc
// @Summary 更新角色权限
// @Schemes
// @Description 更新指定角色的权限列表
// @Tags 权限模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body v1.UpdateRolePermissionRequest true "参数"
// @Success 200 {object} v1.Response
// @Router /v1/admin/role/permissions [put]
func (h *AdminHandler) UpdateRolePermission(ctx *gin.Context) {
var req v1.UpdateRolePermissionRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
err := h.adminService.UpdateRolePermission(ctx, &req)
if err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, nil)
}
// MenuUpdate godoc
// @Summary 更新菜单
// @Schemes
// @Description 更新菜单信息
// @Tags 菜单模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body v1.MenuUpdateRequest true "参数"
// @Success 200 {object} v1.Response
// @Router /v1/admin/menu [put]
func (h *AdminHandler) MenuUpdate(ctx *gin.Context) {
var req v1.MenuUpdateRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
if err := h.adminService.MenuUpdate(ctx, &req); err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, nil)
}
// MenuCreate godoc
// @Summary 创建菜单
// @Schemes
// @Description 创建新的菜单
// @Tags 菜单模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body v1.MenuCreateRequest true "参数"
// @Success 200 {object} v1.Response
// @Router /v1/admin/menu [post]
func (h *AdminHandler) MenuCreate(ctx *gin.Context) {
var req v1.MenuCreateRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
if err := h.adminService.MenuCreate(ctx, &req); err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, nil)
}
// MenuDelete godoc
// @Summary 删除菜单
// @Schemes
// @Description 删除指定菜单
// @Tags 菜单模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param id query uint true "菜单ID"
// @Success 200 {object} v1.Response
// @Router /v1/admin/menu [delete]
func (h *AdminHandler) MenuDelete(ctx *gin.Context) {
var req v1.MenuDeleteRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
if err := h.adminService.MenuDelete(ctx, req.ID); err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, nil)
}
// GetRoles godoc
// @Summary 获取角色列表
// @Schemes
// @Description 获取角色列表
// @Tags 角色模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param page query int true "页码"
// @Param pageSize query int true "每页数量"
// @Param sid query string false "角色ID"
// @Param name query string false "角色名称"
// @Success 200 {object} v1.GetRolesResponse
// @Router /v1/admin/roles [get]
func (h *AdminHandler) GetRoles(ctx *gin.Context) {
var req v1.GetRoleListRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
data, err := h.adminService.GetRoles(ctx, &req)
if err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, data)
}
// RoleCreate godoc
// @Summary 创建角色
// @Schemes
// @Description 创建新的角色
// @Tags 角色模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body v1.RoleCreateRequest true "参数"
// @Success 200 {object} v1.Response
// @Router /v1/admin/role [post]
func (h *AdminHandler) RoleCreate(ctx *gin.Context) {
var req v1.RoleCreateRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
if err := h.adminService.RoleCreate(ctx, &req); err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, nil)
}
// RoleUpdate godoc
// @Summary 更新角色
// @Schemes
// @Description 更新角色信息
// @Tags 角色模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body v1.RoleUpdateRequest true "参数"
// @Success 200 {object} v1.Response
// @Router /v1/admin/role [put]
func (h *AdminHandler) RoleUpdate(ctx *gin.Context) {
var req v1.RoleUpdateRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
if err := h.adminService.RoleUpdate(ctx, &req); err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, nil)
}
// RoleDelete godoc
// @Summary 删除角色
// @Schemes
// @Description 删除指定角色
// @Tags 角色模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param id query uint true "角色ID"
// @Success 200 {object} v1.Response
// @Router /v1/admin/role [delete]
func (h *AdminHandler) RoleDelete(ctx *gin.Context) {
var req v1.RoleDeleteRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
if err := h.adminService.RoleDelete(ctx, req.ID); err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, nil)
}
// GetApis godoc
// @Summary 获取API列表
// @Schemes
// @Description 获取API列表
// @Tags API模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param page query int true "页码"
// @Param pageSize query int true "每页数量"
// @Param group query string false "API分组"
// @Param name query string false "API名称"
// @Param path query string false "API路径"
// @Param method query string false "请求方法"
// @Success 200 {object} v1.GetApisResponse
// @Router /v1/admin/apis [get]
func (h *AdminHandler) GetApis(ctx *gin.Context) {
var req v1.GetApisRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
data, err := h.adminService.GetApis(ctx, &req)
if err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, data)
}
// ApiCreate godoc
// @Summary 创建API
// @Schemes
// @Description 创建新的API
// @Tags API模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body v1.ApiCreateRequest true "参数"
// @Success 200 {object} v1.Response
// @Router /v1/admin/api [post]
func (h *AdminHandler) ApiCreate(ctx *gin.Context) {
var req v1.ApiCreateRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
if err := h.adminService.ApiCreate(ctx, &req); err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, nil)
}
// ApiUpdate godoc
// @Summary 更新API
// @Schemes
// @Description 更新API信息
// @Tags API模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body v1.ApiUpdateRequest true "参数"
// @Success 200 {object} v1.Response
// @Router /v1/admin/api [put]
func (h *AdminHandler) ApiUpdate(ctx *gin.Context) {
var req v1.ApiUpdateRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
if err := h.adminService.ApiUpdate(ctx, &req); err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, nil)
}
// ApiDelete godoc
// @Summary 删除API
// @Schemes
// @Description 删除指定API
// @Tags API模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param id query uint true "API ID"
// @Success 200 {object} v1.Response
// @Router /v1/admin/api [delete]
func (h *AdminHandler) ApiDelete(ctx *gin.Context) {
var req v1.ApiDeleteRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
if err := h.adminService.ApiDelete(ctx, req.ID); err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, nil)
}
// AdminUserUpdate godoc
// @Summary 更新管理员用户
// @Schemes
// @Description 更新管理员用户信息
// @Tags 用户模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body v1.AdminUserUpdateRequest true "参数"
// @Success 200 {object} v1.Response
// @Router /v1/admin/user [put]
func (h *AdminHandler) AdminUserUpdate(ctx *gin.Context) {
var req v1.AdminUserUpdateRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
if err := h.adminService.AdminUserUpdate(ctx, &req); err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, nil)
}
// AdminUserCreate godoc
// @Summary 创建管理员用户
// @Schemes
// @Description 创建新的管理员用户
// @Tags 用户模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body v1.AdminUserCreateRequest true "参数"
// @Success 200 {object} v1.Response
// @Router /v1/admin/user [post]
func (h *AdminHandler) AdminUserCreate(ctx *gin.Context) {
var req v1.AdminUserCreateRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
if err := h.adminService.AdminUserCreate(ctx, &req); err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, nil)
}
// AdminUserDelete godoc
// @Summary 删除管理员用户
// @Schemes
// @Description 删除指定管理员用户
// @Tags 用户模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param id query uint true "用户ID"
// @Success 200 {object} v1.Response
// @Router /v1/admin/user [delete]
func (h *AdminHandler) AdminUserDelete(ctx *gin.Context) {
var req v1.AdminUserDeleteRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
if err := h.adminService.AdminUserDelete(ctx, req.ID); err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, nil)
}
// GetAdminUsers godoc
// @Summary 获取管理员用户列表
// @Schemes
// @Description 获取管理员用户列表
// @Tags 用户模块
// @Accept json
// @Produce json
// @Security Bearer
// @Param page query int true "页码"
// @Param pageSize query int true "每页数量"
// @Param username query string false "用户名"
// @Param nickname query string false "昵称"
// @Param phone query string false "手机号"
// @Param email query string false "邮箱"
// @Success 200 {object} v1.GetAdminUsersResponse
// @Router /v1/admin/users [get]
func (h *AdminHandler) GetAdminUsers(ctx *gin.Context) {
var req v1.GetAdminUsersRequest
if err := ctx.ShouldBind(&req); err != nil {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
return
}
data, err := h.adminService.GetAdminUsers(ctx, &req)
if err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, data)
}
// GetAdminUser godoc
// @Summary 获取管理用户信息
// @Schemes
// @Description
// @Tags 用户模块
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} v1.GetAdminUserResponse
// @Router /v1/admin/user [get]
func (h *AdminHandler) GetAdminUser(ctx *gin.Context) {
data, err := h.adminService.GetAdminUser(ctx, GetUserIdFromCtx(ctx))
if err != nil {
v1.HandleError(ctx, http.StatusInternalServerError, v1.ErrInternalServerError, nil)
return
}
v1.HandleSuccess(ctx, data)
}

View File

@ -0,0 +1,26 @@
package handler
import (
"github.com/gin-gonic/gin"
"nunu-layout-admin/pkg/jwt"
"nunu-layout-admin/pkg/log"
)
type Handler struct {
logger *log.Logger
}
func NewHandler(
logger *log.Logger,
) *Handler {
return &Handler{
logger: logger,
}
}
func GetUserIdFromCtx(ctx *gin.Context) uint {
v, exists := ctx.Get("claims")
if !exists {
return 0
}
return v.(*jwt.MyCustomClaims).UserId
}

25
internal/handler/user.go Normal file
View File

@ -0,0 +1,25 @@
package handler
import (
"github.com/gin-gonic/gin"
"nunu-layout-admin/internal/service"
)
type UserHandler struct {
*Handler
userService service.UserService
}
func NewUserHandler(
handler *Handler,
userService service.UserService,
) *UserHandler {
return &UserHandler{
Handler: handler,
userService: userService,
}
}
func (h *UserHandler) GetUsers(ctx *gin.Context) {
}

27
internal/job/job.go Normal file
View File

@ -0,0 +1,27 @@
package job
import (
"nunu-layout-admin/internal/repository"
"nunu-layout-admin/pkg/jwt"
"nunu-layout-admin/pkg/log"
"nunu-layout-admin/pkg/sid"
)
type Job struct {
logger *log.Logger
sid *sid.Sid
jwt *jwt.JWT
tm repository.Transaction
}
func NewJob(
tm repository.Transaction,
logger *log.Logger,
sid *sid.Sid,
) *Job {
return &Job{
logger: logger,
sid: sid,
tm: tm,
}
}

30
internal/job/user.go Normal file
View File

@ -0,0 +1,30 @@
package job
import (
"context"
"nunu-layout-admin/internal/repository"
)
type UserJob interface {
KafkaConsumer(ctx context.Context) error
}
func NewUserJob(
job *Job,
userRepo repository.UserRepository,
) UserJob {
return &userJob{
userRepo: userRepo,
Job: job,
}
}
type userJob struct {
userRepo repository.UserRepository
*Job
}
func (t userJob) KafkaConsumer(ctx context.Context) error {
// do something
return nil
}

View File

@ -0,0 +1,23 @@
package middleware
import (
"github.com/gin-gonic/gin"
"net/http"
)
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", c.GetHeader("Origin"))
c.Header("Access-Control-Allow-Credentials", "true")
if method == "OPTIONS" {
c.Header("Access-Control-Allow-Methods", c.GetHeader("Access-Control-Request-Method"))
c.Header("Access-Control-Allow-Headers", c.GetHeader("Access-Control-Request-Headers"))
c.Header("Access-Control-Max-Age", "7200")
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}

View File

@ -0,0 +1,72 @@
package middleware
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"net/http"
"nunu-layout-admin/api/v1"
"nunu-layout-admin/pkg/jwt"
"nunu-layout-admin/pkg/log"
)
func StrictAuth(j *jwt.JWT, logger *log.Logger) gin.HandlerFunc {
return func(ctx *gin.Context) {
tokenString := ctx.Request.Header.Get("Authorization")
if tokenString == "" {
logger.WithContext(ctx).Warn("No token", zap.Any("data", map[string]interface{}{
"url": ctx.Request.URL,
"params": ctx.Params,
}))
v1.HandleError(ctx, http.StatusUnauthorized, v1.ErrUnauthorized, nil)
ctx.Abort()
return
}
claims, err := j.ParseToken(tokenString)
if err != nil {
logger.WithContext(ctx).Error("token error", zap.Any("data", map[string]interface{}{
"url": ctx.Request.URL,
"params": ctx.Params,
}), zap.Error(err))
v1.HandleError(ctx, http.StatusUnauthorized, v1.ErrUnauthorized, nil)
ctx.Abort()
return
}
ctx.Set("claims", claims)
recoveryLoggerFunc(ctx, logger)
ctx.Next()
}
}
func NoStrictAuth(j *jwt.JWT, logger *log.Logger) gin.HandlerFunc {
return func(ctx *gin.Context) {
tokenString := ctx.Request.Header.Get("Authorization")
if tokenString == "" {
tokenString, _ = ctx.Cookie("accessToken")
}
if tokenString == "" {
tokenString = ctx.Query("accessToken")
}
if tokenString == "" {
ctx.Next()
return
}
claims, err := j.ParseToken(tokenString)
if err != nil {
ctx.Next()
return
}
ctx.Set("claims", claims)
recoveryLoggerFunc(ctx, logger)
ctx.Next()
}
}
func recoveryLoggerFunc(ctx *gin.Context, logger *log.Logger) {
if userInfo, ok := ctx.MustGet("claims").(*jwt.MyCustomClaims); ok {
logger.WithValue(ctx, zap.Any("UserId", userInfo.UserId))
}
}

View File

@ -0,0 +1,54 @@
package middleware
import (
"bytes"
"github.com/duke-git/lancet/v2/cryptor"
"github.com/duke-git/lancet/v2/random"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"io"
"nunu-layout-admin/pkg/log"
"time"
)
func RequestLogMiddleware(logger *log.Logger) gin.HandlerFunc {
return func(ctx *gin.Context) {
// The configuration is initialized once per request
uuid, err := random.UUIdV4()
if err != nil {
return
}
trace := cryptor.Md5String(uuid)
logger.WithValue(ctx, zap.String("trace", trace))
logger.WithValue(ctx, zap.String("request_method", ctx.Request.Method))
logger.WithValue(ctx, zap.Any("request_headers", ctx.Request.Header))
logger.WithValue(ctx, zap.String("request_url", ctx.Request.URL.String()))
if ctx.Request.Body != nil {
bodyBytes, _ := ctx.GetRawData()
ctx.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) // 关键点
logger.WithValue(ctx, zap.String("request_params", string(bodyBytes)))
}
logger.WithContext(ctx).Info("Request")
ctx.Next()
}
}
func ResponseLogMiddleware(logger *log.Logger) gin.HandlerFunc {
return func(ctx *gin.Context) {
blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: ctx.Writer}
ctx.Writer = blw
startTime := time.Now()
ctx.Next()
duration := time.Since(startTime).String()
logger.WithContext(ctx).Info("Response", zap.Any("response_body", blw.body.String()), zap.Any("time", duration))
}
}
type bodyLogWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (w bodyLogWriter) Write(b []byte) (int, error) {
w.body.Write(b)
return w.ResponseWriter.Write(b)
}

View File

@ -0,0 +1,49 @@
package middleware
import (
"github.com/casbin/casbin/v2"
"github.com/duke-git/lancet/v2/convertor"
"github.com/gin-gonic/gin"
"net/http"
v1 "nunu-layout-admin/api/v1"
"nunu-layout-admin/internal/model"
"nunu-layout-admin/pkg/jwt"
)
func AuthMiddleware(e *casbin.SyncedEnforcer) gin.HandlerFunc {
return func(ctx *gin.Context) {
// 从上下文获取用户信息(假设通过 JWT 或其他方式设置)
v, exists := ctx.Get("claims")
if !exists {
v1.HandleError(ctx, http.StatusUnauthorized, v1.ErrUnauthorized, nil)
ctx.Abort()
return
}
uid := v.(*jwt.MyCustomClaims).UserId
if convertor.ToString(uid) == model.AdminUserID {
// 防呆设计超管跳过API权限检查
ctx.Next()
return
}
// 获取请求的资源和操作
sub := convertor.ToString(uid)
obj := model.ApiResourcePrefix + ctx.Request.URL.Path
act := ctx.Request.Method
// 检查权限
allowed, err := e.Enforce(sub, obj, act)
if err != nil {
v1.HandleError(ctx, http.StatusForbidden, v1.ErrForbidden, nil)
ctx.Abort()
return
}
if !allowed {
v1.HandleError(ctx, http.StatusForbidden, v1.ErrForbidden, nil)
ctx.Abort()
return
}
ctx.Next()
}
}

View File

@ -0,0 +1,53 @@
package middleware
import (
"github.com/duke-git/lancet/v2/cryptor"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
"net/http"
v1 "nunu-layout-admin/api/v1"
"nunu-layout-admin/pkg/log"
"sort"
"strings"
)
func SignMiddleware(logger *log.Logger, conf *viper.Viper) gin.HandlerFunc {
return func(ctx *gin.Context) {
requiredHeaders := []string{"Timestamp", "Nonce", "Sign", "App-Version"}
for _, header := range requiredHeaders {
value, ok := ctx.Request.Header[header]
if !ok || len(value) == 0 {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
ctx.Abort()
return
}
}
data := map[string]string{
"AppKey": conf.GetString("security.api_sign.app_key"),
"Timestamp": ctx.Request.Header.Get("Timestamp"),
"Nonce": ctx.Request.Header.Get("Nonce"),
"AppVersion": ctx.Request.Header.Get("App-Version"),
}
var keys []string
for k := range data {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool { return strings.ToLower(keys[i]) < strings.ToLower(keys[j]) })
var str string
for _, k := range keys {
str += k + data[k]
}
str += conf.GetString("security.api_sign.app_security")
if ctx.Request.Header.Get("Sign") != strings.ToUpper(cryptor.Md5String(str)) {
v1.HandleError(ctx, http.StatusBadRequest, v1.ErrBadRequest, nil)
ctx.Abort()
return
}
ctx.Next()
}
}

46
internal/model/admin.go Normal file
View File

@ -0,0 +1,46 @@
package model
import "gorm.io/gorm"
const (
AdminRole = "admin"
AdminUserID = "1"
MenuResourcePrefix = "menu:"
ApiResourcePrefix = "api:"
PermSep = ","
)
type AdminUser struct {
gorm.Model
Username string `gorm:"type:varchar(50);not null;uniqueIndex;comment:'用户名'"`
Nickname string `gorm:"type:varchar(50);not null;comment:'昵称'"`
Password string `gorm:"type:varchar(255);not null;comment:'密码'"`
Email string `gorm:"type:varchar(100);not null;comment:'电子邮件'"`
Phone string `gorm:"type:varchar(20);not null;comment:'手机号'"`
}
func (m *AdminUser) TableName() string {
return "admin_users"
}
type Role struct {
gorm.Model
Name string `json:"name" gorm:"column:name;type:varchar(100);uniqueIndex;comment:角色名"`
Sid string `json:"sid" gorm:"column:sid;type:varchar(100);uniqueIndex;comment:角色标识"`
}
func (m *Role) TableName() string {
return "roles"
}
type Api struct {
gorm.Model
Group string `gorm:"type:varchar(100);not null;comment:'API分组'"`
Name string `gorm:"type:varchar(100);not null;comment:'API名称'"`
Path string `gorm:"type:varchar(255);not null;comment:'API路径'"`
Method string `gorm:"type:varchar(20);not null;comment:'HTTP方法'"`
}
func (m *Api) TableName() string {
return "api"
}

24
internal/model/menu.go Normal file
View File

@ -0,0 +1,24 @@
package model
import "gorm.io/gorm"
type Menu struct {
gorm.Model
ParentID uint `json:"parentId,omitempty" gorm:"column:parent_id;index;comment:父级菜单的id使用整数表示"` // 父级菜单的id使用整数表示
Path string `json:"path" gorm:"column:path;type:varchar(255);comment:地址"` // 地址
Title string `json:"title" gorm:"column:title;type:varchar(100);comment:标题,使用字符串表示"` // 标题,使用字符串表示
Name string `json:"name,omitempty" gorm:"column:name;type:varchar(100);comment:同路由中的name用于保活"` // 同路由中的name用于保活
Component string `json:"component,omitempty" gorm:"column:component;type:varchar(255);comment:绑定的组件"` // 绑定的组件默认类型Iframe、RouteView、ComponentError
Locale string `json:"locale,omitempty" gorm:"column:locale;type:varchar(100);comment:本地化标识"` // 本地化标识
Icon string `json:"icon,omitempty" gorm:"column:icon;type:varchar(100);comment:图标,使用字符串表示"` // 图标,使用字符串表示
Redirect string `json:"redirect,omitempty" gorm:"column:redirect;type:varchar(255);comment:重定向地址"` // 重定向地址
URL string `json:"url,omitempty" gorm:"column:url;type:varchar(255);comment:iframe模式下的跳转url"` // iframe模式下的跳转url不能与path重复
KeepAlive bool `json:"keepAlive,omitempty" gorm:"column:keep_alive;default:false;comment:是否保活"` // 是否保活
HideInMenu bool `json:"hideInMenu,omitempty" gorm:"column:hide_in_menu;default:false;comment:是否保活"` // 是否保活
Target string `json:"target,omitempty" gorm:"column:target;type:varchar(20);comment:全连接跳转模式"` // 全连接跳转模式:'_blank'、'_self'、'_parent'
Weight int `json:"weight" gorm:"column:weight;type:int;default:0;comment:排序权重"`
}
func (m *Menu) TableName() string {
return "menu"
}

11
internal/model/user.go Normal file
View File

@ -0,0 +1,11 @@
package model
import "gorm.io/gorm"
type User struct {
gorm.Model
}
func (m *User) TableName() string {
return "user"
}

View File

@ -0,0 +1,327 @@
package repository
import (
"context"
"fmt"
"github.com/duke-git/lancet/v2/convertor"
"go.uber.org/zap"
v1 "nunu-layout-admin/api/v1"
"nunu-layout-admin/internal/model"
"strings"
)
type AdminRepository interface {
GetAdminUsers(ctx context.Context, req *v1.GetAdminUsersRequest) ([]model.AdminUser, int64, error)
GetAdminUser(ctx context.Context, uid uint) (model.AdminUser, error)
GetAdminUserByUsername(ctx context.Context, username string) (model.AdminUser, error)
AdminUserUpdate(ctx context.Context, m *model.AdminUser) error
AdminUserCreate(ctx context.Context, m *model.AdminUser) error
AdminUserDelete(ctx context.Context, id uint) error
GetUserPermissions(ctx context.Context, uid uint) ([][]string, error)
GetUserRoles(ctx context.Context, uid uint) ([]string, error)
GetRolePermissions(ctx context.Context, role string) ([][]string, error)
UpdateRolePermission(ctx context.Context, role string, permissions map[string]struct{}) error
UpdateUserRoles(ctx context.Context, uid uint, roles []string) error
DeleteUserRoles(ctx context.Context, uid uint) error
GetMenuList(ctx context.Context) ([]model.Menu, error)
MenuUpdate(ctx context.Context, m *model.Menu) error
MenuCreate(ctx context.Context, m *model.Menu) error
MenuDelete(ctx context.Context, id uint) error
GetRoles(ctx context.Context, req *v1.GetRoleListRequest) ([]model.Role, int64, error)
RoleUpdate(ctx context.Context, m *model.Role) error
RoleCreate(ctx context.Context, m *model.Role) error
RoleDelete(ctx context.Context, id uint) error
CasbinRoleDelete(ctx context.Context, role string) error
GetRole(ctx context.Context, id uint) (model.Role, error)
GetRoleBySid(ctx context.Context, sid string) (model.Role, error)
GetApis(ctx context.Context, req *v1.GetApisRequest) ([]model.Api, int64, error)
GetApiGroups(ctx context.Context) ([]string, error)
ApiUpdate(ctx context.Context, m *model.Api) error
ApiCreate(ctx context.Context, m *model.Api) error
ApiDelete(ctx context.Context, id uint) error
}
func NewAdminRepository(
repository *Repository,
) AdminRepository {
return &adminRepository{
Repository: repository,
}
}
type adminRepository struct {
*Repository
}
func (r *adminRepository) CasbinRoleDelete(ctx context.Context, role string) error {
_, err := r.e.DeleteRole(role)
return err
}
func (r *adminRepository) GetRole(ctx context.Context, id uint) (model.Role, error) {
m := model.Role{}
return m, r.DB(ctx).Where("id = ?", id).First(&m).Error
}
func (r *adminRepository) GetRoleBySid(ctx context.Context, sid string) (model.Role, error) {
m := model.Role{}
return m, r.DB(ctx).Where("sid = ?", sid).First(&m).Error
}
func (r *adminRepository) DeleteUserRoles(ctx context.Context, uid uint) error {
_, err := r.e.DeleteRolesForUser(convertor.ToString(uid))
return err
}
func (r *adminRepository) UpdateUserRoles(ctx context.Context, uid uint, roles []string) error {
if len(roles) == 0 {
_, err := r.e.DeleteRolesForUser(convertor.ToString(uid))
return err
}
old, err := r.e.GetRolesForUser(convertor.ToString(uid))
if err != nil {
return err
}
oldMap := make(map[string]struct{})
newMap := make(map[string]struct{})
for _, v := range old {
oldMap[v] = struct{}{}
}
for _, v := range roles {
newMap[v] = struct{}{}
}
addRoles := make([]string, 0)
delRoles := make([]string, 0)
for key, _ := range oldMap {
if _, exists := newMap[key]; !exists {
delRoles = append(delRoles, key)
}
}
for key, _ := range newMap {
if _, exists := oldMap[key]; !exists {
addRoles = append(addRoles, key)
}
}
if len(addRoles) == 0 && len(delRoles) == 0 {
return nil
}
for _, role := range delRoles {
if _, err := r.e.DeleteRoleForUser(convertor.ToString(uid), role); err != nil {
r.logger.WithContext(ctx).Error("DeleteRoleForUser error", zap.Error(err))
return err
}
}
_, err = r.e.AddRolesForUser(convertor.ToString(uid), addRoles)
return err
}
func (r *adminRepository) GetAdminUserByUsername(ctx context.Context, username string) (model.AdminUser, error) {
m := model.AdminUser{}
return m, r.DB(ctx).Where("username = ?", username).First(&m).Error
}
func (r *adminRepository) GetAdminUsers(ctx context.Context, req *v1.GetAdminUsersRequest) ([]model.AdminUser, int64, error) {
var list []model.AdminUser
var total int64
scope := r.DB(ctx).Model(&model.AdminUser{})
if req.Username != "" {
scope = scope.Where("username LIKE ?", "%"+req.Username+"%")
}
if req.Nickname != "" {
scope = scope.Where("nickname LIKE ?", "%"+req.Nickname+"%")
}
if req.Email != "" {
scope = scope.Where("email LIKE ?", "%"+req.Email+"%")
}
if req.Phone != "" {
scope = scope.Where("phone LIKE ?", "%"+req.Phone+"%")
}
if err := scope.Count(&total).Error; err != nil {
return nil, total, err
}
if err := scope.Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Order("id DESC").Find(&list).Error; err != nil {
return nil, total, err
}
return list, total, nil
}
func (r *adminRepository) GetAdminUser(ctx context.Context, uid uint) (model.AdminUser, error) {
m := model.AdminUser{}
return m, r.DB(ctx).Where("id = ?", uid).First(&m).Error
}
func (r *adminRepository) AdminUserUpdate(ctx context.Context, m *model.AdminUser) error {
return r.DB(ctx).Where("id = ?", m.ID).Save(m).Error
}
func (r *adminRepository) AdminUserCreate(ctx context.Context, m *model.AdminUser) error {
return r.DB(ctx).Create(m).Error
}
func (r *adminRepository) AdminUserDelete(ctx context.Context, id uint) error {
return r.DB(ctx).Where("id = ?", id).Delete(&model.AdminUser{}).Error
}
func (r *adminRepository) UpdateRolePermission(ctx context.Context, role string, newPermSet map[string]struct{}) error {
if len(newPermSet) == 0 {
return nil
}
// 获取当前角色的所有权限
oldPermissions, err := r.e.GetPermissionsForUser(role)
if err != nil {
return err
}
// 将旧权限转换为 map 方便查找
oldPermSet := make(map[string]struct{})
for _, perm := range oldPermissions {
if len(perm) == 3 {
oldPermSet[strings.Join([]string{perm[1], perm[2]}, model.PermSep)] = struct{}{}
}
}
// 找出需要删除的权限
var removePermissions [][]string
for key, _ := range oldPermSet {
if _, exists := newPermSet[key]; !exists {
removePermissions = append(removePermissions, strings.Split(key, model.PermSep))
}
}
// 找出需要添加的权限
var addPermissions [][]string
for key, _ := range newPermSet {
if _, exists := oldPermSet[key]; !exists {
addPermissions = append(addPermissions, strings.Split(key, model.PermSep))
}
}
// 先移除多余的权限(使用 DeletePermissionForUser 逐条删除)
for _, perm := range removePermissions {
_, err := r.e.DeletePermissionForUser(role, perm...)
if err != nil {
return fmt.Errorf("移除权限失败: %v", err)
}
}
// 再添加新的权限
if len(addPermissions) > 0 {
_, err = r.e.AddPermissionsForUser(role, addPermissions...)
if err != nil {
return fmt.Errorf("添加新权限失败: %v", err)
}
}
return nil
}
func (r *adminRepository) GetApiGroups(ctx context.Context) ([]string, error) {
res := make([]string, 0)
if err := r.DB(ctx).Model(&model.Api{}).Group("`group`").Pluck("`group`", &res).Error; err != nil {
return nil, err
}
return res, nil
}
func (r *adminRepository) GetApis(ctx context.Context, req *v1.GetApisRequest) ([]model.Api, int64, error) {
var list []model.Api
var total int64
scope := r.DB(ctx).Model(&model.Api{})
if req.Name != "" {
scope = scope.Where("name LIKE ?", "%"+req.Name+"%")
}
if req.Group != "" {
scope = scope.Where("`group` LIKE ?", "%"+req.Group+"%")
}
if req.Path != "" {
scope = scope.Where("path LIKE ?", "%"+req.Path+"%")
}
if req.Method != "" {
scope = scope.Where("method = ?", req.Method)
}
if err := scope.Count(&total).Error; err != nil {
return nil, total, err
}
if err := scope.Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Order("`group` ASC").Find(&list).Error; err != nil {
return nil, total, err
}
return list, total, nil
}
func (r *adminRepository) ApiUpdate(ctx context.Context, m *model.Api) error {
return r.DB(ctx).Where("id = ?", m.ID).Save(m).Error
}
func (r *adminRepository) ApiCreate(ctx context.Context, m *model.Api) error {
return r.DB(ctx).Create(m).Error
}
func (r *adminRepository) ApiDelete(ctx context.Context, id uint) error {
return r.DB(ctx).Where("id = ?", id).Delete(&model.Api{}).Error
}
func (r *adminRepository) GetUserPermissions(ctx context.Context, uid uint) ([][]string, error) {
return r.e.GetImplicitPermissionsForUser(convertor.ToString(uid))
}
func (r *adminRepository) GetRolePermissions(ctx context.Context, role string) ([][]string, error) {
return r.e.GetPermissionsForUser(role)
}
func (r *adminRepository) GetUserRoles(ctx context.Context, uid uint) ([]string, error) {
return r.e.GetRolesForUser(convertor.ToString(uid))
}
func (r *adminRepository) MenuUpdate(ctx context.Context, m *model.Menu) error {
return r.DB(ctx).Where("id = ?", m.ID).Save(m).Error
}
func (r *adminRepository) MenuCreate(ctx context.Context, m *model.Menu) error {
return r.DB(ctx).Save(m).Error
}
func (r *adminRepository) MenuDelete(ctx context.Context, id uint) error {
return r.DB(ctx).Where("id = ?", id).Delete(&model.Menu{}).Error
}
func (r *adminRepository) GetMenuList(ctx context.Context) ([]model.Menu, error) {
var menuList []model.Menu
if err := r.DB(ctx).Order("weight DESC").Find(&menuList).Error; err != nil {
return nil, err
}
return menuList, nil
}
func (r *adminRepository) RoleUpdate(ctx context.Context, m *model.Role) error {
return r.DB(ctx).Where("id = ?", m.ID).UpdateColumn("name", m.Name).Error
}
func (r *adminRepository) RoleCreate(ctx context.Context, m *model.Role) error {
return r.DB(ctx).Create(m).Error
}
func (r *adminRepository) RoleDelete(ctx context.Context, id uint) error {
return r.DB(ctx).Where("id = ?", id).Delete(&model.Role{}).Error
}
func (r *adminRepository) GetRoles(ctx context.Context, req *v1.GetRoleListRequest) ([]model.Role, int64, error) {
var list []model.Role
var total int64
scope := r.DB(ctx).Model(&model.Role{})
if req.Name != "" {
scope = scope.Where("name LIKE ?", "%"+req.Name+"%")
}
if req.Sid != "" {
scope = scope.Where("sid = ?", req.Sid)
}
if err := scope.Count(&total).Error; err != nil {
return nil, total, err
}
if err := scope.Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Find(&list).Error; err != nil {
return nil, total, err
}
return list, total, nil
}

View File

@ -0,0 +1,160 @@
package repository
import (
"context"
"fmt"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
gormadapter "github.com/casbin/gorm-adapter/v3"
"github.com/glebarez/sqlite"
"github.com/redis/go-redis/v9"
"github.com/spf13/viper"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"nunu-layout-admin/pkg/log"
"nunu-layout-admin/pkg/zapgorm2"
"time"
)
const ctxTxKey = "TxKey"
type Repository struct {
db *gorm.DB
e *casbin.SyncedEnforcer
//rdb *redis.Client
logger *log.Logger
}
func NewRepository(
logger *log.Logger,
db *gorm.DB,
e *casbin.SyncedEnforcer,
// rdb *redis.Client,
) *Repository {
return &Repository{
db: db,
e: e,
//rdb: rdb,
logger: logger,
}
}
type Transaction interface {
Transaction(ctx context.Context, fn func(ctx context.Context) error) error
}
func NewTransaction(r *Repository) Transaction {
return r
}
// DB return tx
// If you need to create a Transaction, you must call DB(ctx) and Transaction(ctx,fn)
func (r *Repository) DB(ctx context.Context) *gorm.DB {
v := ctx.Value(ctxTxKey)
if v != nil {
if tx, ok := v.(*gorm.DB); ok {
return tx
}
}
return r.db.WithContext(ctx)
}
func (r *Repository) Transaction(ctx context.Context, fn func(ctx context.Context) error) error {
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
ctx = context.WithValue(ctx, ctxTxKey, tx)
return fn(ctx)
})
}
func NewDB(conf *viper.Viper, l *log.Logger) *gorm.DB {
var (
db *gorm.DB
err error
)
logger := zapgorm2.New(l.Logger)
driver := conf.GetString("data.db.user.driver")
dsn := conf.GetString("data.db.user.dsn")
// GORM doc: https://gorm.io/docs/connecting_to_the_database.html
switch driver {
case "mysql":
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger,
})
case "postgres":
db, err = gorm.Open(postgres.New(postgres.Config{
DSN: dsn,
PreferSimpleProtocol: true, // disables implicit prepared statement usage
}), &gorm.Config{})
case "sqlite":
db, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{})
default:
panic("unknown db driver")
}
if err != nil {
panic(err)
}
db = db.Debug()
// Connection Pool config
sqlDB, err := db.DB()
if err != nil {
panic(err)
}
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
return db
}
func NewCasbinEnforcer(conf *viper.Viper, l *log.Logger, db *gorm.DB) *casbin.SyncedEnforcer {
a, _ := gormadapter.NewAdapterByDB(db)
m, err := model.NewModelFromString(`
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`)
if err != nil {
panic(err)
}
e, _ := casbin.NewSyncedEnforcer(m, a)
e.StartAutoLoadPolicy(10 * time.Second) // 每10秒自动加载策略防止启动多服务进程策略不一致
// Enable Logger, decide whether to show it in terminal
//e.EnableLog(true)
// Save the policy back to DB.
e.EnableAutoSave(true)
return e
}
func NewRedis(conf *viper.Viper) *redis.Client {
rdb := redis.NewClient(&redis.Options{
Addr: conf.GetString("data.redis.addr"),
Password: conf.GetString("data.redis.password"),
DB: conf.GetInt("data.redis.db"),
})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := rdb.Ping(ctx).Result()
if err != nil {
panic(fmt.Sprintf("redis error: %s", err.Error()))
}
return rdb
}

View File

@ -0,0 +1,28 @@
package repository
import (
"context"
"nunu-layout-admin/internal/model"
)
type UserRepository interface {
GetUser(ctx context.Context, id int64) (*model.User, error)
}
func NewUserRepository(
repository *Repository,
) UserRepository {
return &userRepository{
Repository: repository,
}
}
type userRepository struct {
*Repository
}
func (r *userRepository) GetUser(ctx context.Context, id int64) (*model.User, error) {
var user model.User
return &user, nil
}

101
internal/server/http.go Normal file
View File

@ -0,0 +1,101 @@
package server
import (
"github.com/casbin/casbin/v2"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
swaggerfiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
nethttp "net/http"
"nunu-layout-admin/docs"
"nunu-layout-admin/internal/handler"
"nunu-layout-admin/internal/middleware"
"nunu-layout-admin/pkg/jwt"
"nunu-layout-admin/pkg/log"
"nunu-layout-admin/pkg/server/http"
"nunu-layout-admin/web"
)
func NewHTTPServer(
logger *log.Logger,
conf *viper.Viper,
jwt *jwt.JWT,
e *casbin.SyncedEnforcer,
adminHandler *handler.AdminHandler,
userHandler *handler.UserHandler,
) *http.Server {
gin.SetMode(gin.DebugMode)
s := http.NewServer(
gin.Default(),
logger,
http.WithServerHost(conf.GetString("http.host")),
http.WithServerPort(conf.GetInt("http.port")),
)
// 设置前端静态资源
s.Use(static.Serve("/", static.EmbedFolder(web.Assets(), "dist")))
s.NoRoute(func(c *gin.Context) {
indexPageData, err := web.Assets().ReadFile("dist/index.html")
if err != nil {
c.String(nethttp.StatusNotFound, "404 page not found")
return
}
c.Data(nethttp.StatusOK, "text/html; charset=utf-8", indexPageData)
})
// swagger doc
docs.SwaggerInfo.BasePath = "/"
s.GET("/swagger/*any", ginSwagger.WrapHandler(
swaggerfiles.Handler,
//ginSwagger.URL(fmt.Sprintf("http://localhost:%d/swagger/doc.json", conf.GetInt("app.http.port"))),
ginSwagger.DefaultModelsExpandDepth(-1),
ginSwagger.PersistAuthorization(true),
))
s.Use(
middleware.CORSMiddleware(),
middleware.ResponseLogMiddleware(logger),
middleware.RequestLogMiddleware(logger),
//middleware.SignMiddleware(log),
)
v1 := s.Group("/v1")
{
// No route group has permission
noAuthRouter := v1.Group("/")
{
noAuthRouter.POST("/login", adminHandler.Login)
}
// Strict permission routing group
strictAuthRouter := v1.Group("/").Use(middleware.StrictAuth(jwt, logger), middleware.AuthMiddleware(e))
{
strictAuthRouter.GET("/users", userHandler.GetUsers)
strictAuthRouter.GET("/menus", adminHandler.GetMenus)
strictAuthRouter.GET("/admin/menus", adminHandler.GetAdminMenus)
strictAuthRouter.POST("/admin/menu", adminHandler.MenuCreate)
strictAuthRouter.PUT("/admin/menu", adminHandler.MenuUpdate)
strictAuthRouter.DELETE("/admin/menu", adminHandler.MenuDelete)
strictAuthRouter.GET("/admin/users", adminHandler.GetAdminUsers)
strictAuthRouter.GET("/admin/user", adminHandler.GetAdminUser)
strictAuthRouter.PUT("/admin/user", adminHandler.AdminUserUpdate)
strictAuthRouter.POST("/admin/user", adminHandler.AdminUserCreate)
strictAuthRouter.DELETE("/admin/user", adminHandler.AdminUserDelete)
strictAuthRouter.GET("/admin/user/permissions", adminHandler.GetUserPermissions)
strictAuthRouter.GET("/admin/role/permissions", adminHandler.GetRolePermissions)
strictAuthRouter.PUT("/admin/role/permission", adminHandler.UpdateRolePermission)
strictAuthRouter.GET("/admin/roles", adminHandler.GetRoles)
strictAuthRouter.POST("/admin/role", adminHandler.RoleCreate)
strictAuthRouter.PUT("/admin/role", adminHandler.RoleUpdate)
strictAuthRouter.DELETE("/admin/role", adminHandler.RoleDelete)
strictAuthRouter.GET("/admin/apis", adminHandler.GetApis)
strictAuthRouter.POST("/admin/api", adminHandler.ApiCreate)
strictAuthRouter.PUT("/admin/api", adminHandler.ApiUpdate)
strictAuthRouter.DELETE("/admin/api", adminHandler.ApiDelete)
}
}
return s
}

33
internal/server/job.go Normal file
View File

@ -0,0 +1,33 @@
package server
import (
"context"
"nunu-layout-admin/internal/job"
"nunu-layout-admin/pkg/log"
)
type JobServer struct {
log *log.Logger
userJob job.UserJob
}
func NewJobServer(
log *log.Logger,
userJob job.UserJob,
) *JobServer {
return &JobServer{
log: log,
userJob: userJob,
}
}
func (j *JobServer) Start(ctx context.Context) error {
// Tips: If you want job to start as a separate process, just refer to the task implementation and adjust the code accordingly.
// eg: kafka consumer
err := j.userJob.KafkaConsumer(ctx)
return err
}
func (j *JobServer) Stop(ctx context.Context) error {
return nil
}

View File

@ -0,0 +1,697 @@
package server
import (
"context"
"encoding/json"
"fmt"
"github.com/casbin/casbin/v2"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"net/http"
v1 "nunu-layout-admin/api/v1"
"nunu-layout-admin/internal/model"
"nunu-layout-admin/pkg/log"
"nunu-layout-admin/pkg/sid"
"os"
)
type MigrateServer struct {
db *gorm.DB
log *log.Logger
sid *sid.Sid
e *casbin.SyncedEnforcer
}
func NewMigrateServer(
db *gorm.DB,
log *log.Logger,
sid *sid.Sid,
e *casbin.SyncedEnforcer,
) *MigrateServer {
return &MigrateServer{
e: e,
db: db,
log: log,
sid: sid,
}
}
func (m *MigrateServer) Start(ctx context.Context) error {
m.db.Migrator().DropTable(
&model.AdminUser{},
&model.Menu{},
&model.Role{},
&model.Api{},
)
if err := m.db.AutoMigrate(
&model.AdminUser{},
&model.Menu{},
&model.Role{},
&model.Api{},
); err != nil {
m.log.Error("user migrate error", zap.Error(err))
return err
}
err := m.initialAdminUser(ctx)
if err != nil {
m.log.Error("initialAdminUser error", zap.Error(err))
}
err = m.initialMenuData(ctx)
if err != nil {
m.log.Error("initialMenuData error", zap.Error(err))
}
err = m.initialApisData(ctx)
if err != nil {
m.log.Error("initialApisData error", zap.Error(err))
}
err = m.initialRBAC(ctx)
if err != nil {
m.log.Error("initialRBAC error", zap.Error(err))
}
m.log.Info("AutoMigrate success")
os.Exit(0)
return nil
}
func (m *MigrateServer) Stop(ctx context.Context) error {
m.log.Info("AutoMigrate stop")
return nil
}
func (m *MigrateServer) initialAdminUser(ctx context.Context) error {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte("123456"), bcrypt.DefaultCost)
if err != nil {
return err
}
err = m.db.Create(&model.AdminUser{
Model: gorm.Model{ID: 1},
Username: "admin",
Password: string(hashedPassword),
Nickname: "Admin",
}).Error
return m.db.Create(&model.AdminUser{
Model: gorm.Model{ID: 2},
Username: "user",
Password: string(hashedPassword),
Nickname: "运营人员",
}).Error
}
func (m *MigrateServer) initialRBAC(ctx context.Context) error {
roles := []model.Role{
{Sid: model.AdminRole, Name: "超级管理员"},
{Sid: "1000", Name: "运营人员"},
{Sid: "1001", Name: "访客"},
}
if err := m.db.Create(&roles).Error; err != nil {
return err
}
m.e.ClearPolicy()
err := m.e.SavePolicy()
if err != nil {
m.log.Error("m.e.SavePolicy error", zap.Error(err))
return err
}
_, err = m.e.AddRoleForUser(model.AdminUserID, model.AdminRole)
if err != nil {
m.log.Error("m.e.AddRoleForUser error", zap.Error(err))
return err
}
menuList := make([]v1.MenuDataItem, 0)
err = json.Unmarshal([]byte(menuData), &menuList)
if err != nil {
m.log.Error("json.Unmarshal error", zap.Error(err))
return err
}
for _, item := range menuList {
m.addPermissionForRole(model.AdminRole, model.MenuResourcePrefix+item.Path, "read")
}
apiList := make([]model.Api, 0)
err = m.db.Find(&apiList).Error
if err != nil {
m.log.Error("m.db.Find(&apiList).Error error", zap.Error(err))
return err
}
for _, api := range apiList {
m.addPermissionForRole(model.AdminRole, model.ApiResourcePrefix+api.Path, api.Method)
}
// 添加运营人员权限
_, err = m.e.AddRoleForUser("2", "1000")
if err != nil {
m.log.Error("m.e.AddRoleForUser error", zap.Error(err))
return err
}
m.addPermissionForRole("1000", model.MenuResourcePrefix+"/profile/basic", "read")
m.addPermissionForRole("1000", model.MenuResourcePrefix+"/profile/advanced", "read")
m.addPermissionForRole("1000", model.MenuResourcePrefix+"/profile", "read")
m.addPermissionForRole("1000", model.MenuResourcePrefix+"/dashboard", "read")
m.addPermissionForRole("1000", model.MenuResourcePrefix+"/dashboard/workplace", "read")
m.addPermissionForRole("1000", model.MenuResourcePrefix+"/dashboard/analysis", "read")
m.addPermissionForRole("1000", model.MenuResourcePrefix+"/account/settings", "read")
m.addPermissionForRole("1000", model.MenuResourcePrefix+"/account/center", "read")
m.addPermissionForRole("1000", model.MenuResourcePrefix+"/account", "read")
m.addPermissionForRole("1000", model.ApiResourcePrefix+"/v1/menus", http.MethodGet)
m.addPermissionForRole("1000", model.ApiResourcePrefix+"/v1/admin/user", http.MethodGet)
return nil
}
func (m *MigrateServer) addPermissionForRole(role, resource, action string) {
_, err := m.e.AddPermissionForUser(role, resource, action)
if err != nil {
m.log.Sugar().Info("为角色 %s 添加权限 %s:%s 失败: %v", role, resource, action, err)
return
}
fmt.Printf("为角色 %s 添加权限: %s %s\n", role, resource, action)
}
func (m *MigrateServer) initialApisData(ctx context.Context) error {
initialApis := []model.Api{
{Group: "基础API", Name: "获取用户菜单列表", Path: "/v1/menus", Method: http.MethodGet},
{Group: "基础API", Name: "获取管理员信息", Path: "/v1/admin/user", Method: http.MethodGet},
{Group: "菜单管理", Name: "获取管理菜单", Path: "/v1/admin/menus", Method: http.MethodGet},
{Group: "菜单管理", Name: "创建菜单", Path: "/v1/admin/menu", Method: http.MethodPost},
{Group: "菜单管理", Name: "更新菜单", Path: "/v1/admin/menu", Method: http.MethodPut},
{Group: "菜单管理", Name: "删除菜单", Path: "/v1/admin/menu", Method: http.MethodDelete},
{Group: "权限模块", Name: "获取用户权限", Path: "/v1/admin/user/permissions", Method: http.MethodGet},
{Group: "权限模块", Name: "获取角色权限", Path: "/v1/admin/role/permissions", Method: http.MethodGet},
{Group: "权限模块", Name: "更新角色权限", Path: "/v1/admin/role/permission", Method: http.MethodPut},
{Group: "权限模块", Name: "获取角色列表", Path: "/v1/admin/roles", Method: http.MethodGet},
{Group: "权限模块", Name: "创建角色", Path: "/v1/admin/role", Method: http.MethodPost},
{Group: "权限模块", Name: "更新角色", Path: "/v1/admin/role", Method: http.MethodPut},
{Group: "权限模块", Name: "删除角色", Path: "/v1/admin/role", Method: http.MethodDelete},
{Group: "权限模块", Name: "获取管理员列表", Path: "/v1/admin/users", Method: http.MethodGet},
{Group: "权限模块", Name: "更新管理员信息", Path: "/v1/admin/user", Method: http.MethodPut},
{Group: "权限模块", Name: "创建管理员账号", Path: "/v1/admin/user", Method: http.MethodPost},
{Group: "权限模块", Name: "删除管理员", Path: "/v1/admin/user", Method: http.MethodDelete},
{Group: "权限模块", Name: "获取API列表", Path: "/v1/admin/apis", Method: http.MethodGet},
{Group: "权限模块", Name: "创建API", Path: "/v1/admin/api", Method: http.MethodPost},
{Group: "权限模块", Name: "更新API", Path: "/v1/admin/api", Method: http.MethodPut},
{Group: "权限模块", Name: "删除API", Path: "/v1/admin/api", Method: http.MethodDelete},
}
return m.db.Create(&initialApis).Error
}
func (m *MigrateServer) initialMenuData(ctx context.Context) error {
menuList := make([]v1.MenuDataItem, 0)
err := json.Unmarshal([]byte(menuData), &menuList)
if err != nil {
m.log.Error("json.Unmarshal error", zap.Error(err))
return err
}
menuListDb := make([]model.Menu, 0)
for _, item := range menuList {
menuListDb = append(menuListDb, model.Menu{
Model: gorm.Model{
ID: item.ID,
},
ParentID: item.ParentID,
Path: item.Path,
Title: item.Title,
Name: item.Name,
Component: item.Component,
Locale: item.Locale,
Weight: item.Weight,
Icon: item.Icon,
Redirect: item.Redirect,
URL: item.URL,
KeepAlive: item.KeepAlive,
HideInMenu: item.HideInMenu,
})
}
return m.db.Create(&menuListDb).Error
}
var menuData = `[
{
"id": 18,
"parentId": 15,
"path": "/access/admin",
"title": "管理员账号",
"name": "accessAdmin",
"component": "/access/admin",
"locale": "menu.access.admin"
},
{
"id": 2,
"parentId": 0,
"title": "分析页",
"icon": "DashboardOutlined",
"component": "/dashboard/analysis",
"path": "/dashboard/analysis",
"name": "DashboardAnalysis",
"keepAlive": true,
"locale": "menu.dashboard.analysis",
"weight": 2
},
{
"id": 1,
"parentId": 0,
"title": "仪表盘",
"icon": "DashboardOutlined",
"component": "RouteView",
"redirect": "/dashboard/analysis",
"path": "/dashboard",
"name": "Dashboard",
"locale": "menu.dashboard"
},
{
"id": 3,
"parentId": 0,
"title": "表单页",
"icon": "FormOutlined",
"component": "RouteView",
"redirect": "/form/basic",
"path": "/form",
"name": "Form",
"locale": "menu.form"
},
{
"id": 5,
"parentId": 0,
"title": "链接",
"icon": "LinkOutlined",
"component": "RouteView",
"redirect": "/link/iframe",
"path": "/link",
"name": "Link",
"locale": "menu.link"
},
{
"id": 6,
"parentId": 5,
"title": "AntDesign",
"url": "https://ant.design/",
"component": "Iframe",
"path": "/link/iframe",
"name": "LinkIframe",
"keepAlive": true,
"locale": "menu.link.iframe"
},
{
"id": 7,
"parentId": 5,
"title": "AntDesignVue",
"url": "https://antdv.com/",
"component": "Iframe",
"path": "/link/antdv",
"name": "LinkAntdv",
"keepAlive": true,
"locale": "menu.link.antdv"
},
{
"id": 8,
"parentId": 5,
"path": "https://www.baidu.com",
"name": "LinkExternal",
"title": "跳转百度",
"locale": "menu.link.external"
},
{
"id": 9,
"parentId": 0,
"title": "菜单",
"icon": "BarsOutlined",
"component": "RouteView",
"path": "/menu",
"redirect": "/menu/menu1",
"name": "Menu",
"locale": "menu.menu"
},
{
"id": 10,
"parentId": 9,
"title": "菜单1",
"component": "/menu/menu1",
"path": "/menu/menu1",
"name": "MenuMenu11",
"keepAlive": true,
"locale": "menu.menu.menu1"
},
{
"id": 11,
"parentId": 9,
"title": "菜单2",
"component": "/menu/menu2",
"path": "/menu/menu2",
"keepAlive": true,
"locale": "menu.menu.menu2"
},
{
"id": 12,
"parentId": 9,
"path": "/menu/menu3",
"redirect": "/menu/menu3/menu1",
"title": "菜单1-1",
"component": "RouteView",
"locale": "menu.menu.menu3"
},
{
"id": 13,
"parentId": 12,
"path": "/menu/menu3/menu1",
"component": "/menu/menu-1-1/menu1",
"title": "菜单1-1-1",
"keepAlive": true,
"locale": "menu.menu3.menu1"
},
{
"id": 14,
"parentId": 12,
"path": "/menu/menu3/menu2",
"component": "/menu/menu-1-1/menu2",
"title": "菜单1-1-2",
"keepAlive": true,
"locale": "menu.menu3.menu2"
},
{
"id": 15,
"path": "/access",
"component": "RouteView",
"redirect": "/access/common",
"title": "权限模块",
"name": "Access",
"parentId": 0,
"icon": "ClusterOutlined",
"locale": "menu.access",
"weight": 1
},
{
"id": 51,
"parentId": 15,
"path": "/access/role",
"title": "角色管理",
"name": "AccessRoles",
"component": "/access/role",
"locale": "menu.access.roles"
},
{
"id": 52,
"parentId": 15,
"path": "/access/menu",
"title": "菜单管理",
"name": "AccessMenu",
"component": "/access/menu",
"locale": "menu.access.menus"
},
{
"id": 53,
"parentId": 15,
"path": "/access/api",
"title": "API管理",
"name": "AccessAPI",
"component": "/access/api",
"locale": "menu.access.api"
},
{
"id": 19,
"parentId": 0,
"title": "异常页",
"icon": "WarningOutlined",
"component": "RouteView",
"redirect": "/exception/403",
"path": "/exception",
"name": "Exception",
"locale": "menu.exception"
},
{
"id": 20,
"parentId": 19,
"path": "/exception/403",
"title": "403",
"name": "403",
"component": "/exception/403",
"locale": "menu.exception.not-permission"
},
{
"id": 21,
"parentId": 19,
"path": "/exception/404",
"title": "404",
"name": "404",
"component": "/exception/404",
"locale": "menu.exception.not-find"
},
{
"id": 22,
"parentId": 19,
"path": "/exception/500",
"title": "500",
"name": "500",
"component": "/exception/500",
"locale": "menu.exception.server-error"
},
{
"id": 23,
"parentId": 0,
"title": "结果页",
"icon": "CheckCircleOutlined",
"component": "RouteView",
"redirect": "/result/success",
"path": "/result",
"name": "Result",
"locale": "menu.result"
},
{
"id": 24,
"parentId": 23,
"path": "/result/success",
"title": "成功页",
"name": "ResultSuccess",
"component": "/result/success",
"locale": "menu.result.success"
},
{
"id": 25,
"parentId": 23,
"path": "/result/fail",
"title": "失败页",
"name": "ResultFail",
"component": "/result/fail",
"locale": "menu.result.fail"
},
{
"id": 26,
"parentId": 0,
"title": "列表页",
"icon": "TableOutlined",
"component": "RouteView",
"redirect": "/list/card-list",
"path": "/list",
"name": "List",
"locale": "menu.list"
},
{
"id": 27,
"parentId": 26,
"path": "/list/card-list",
"title": "卡片列表",
"name": "ListCard",
"component": "/list/card-list",
"locale": "menu.list.card-list"
},
{
"id": 28,
"parentId": 0,
"title": "详情页",
"icon": "ProfileOutlined",
"component": "RouteView",
"redirect": "/profile/basic",
"path": "/profile",
"name": "Profile",
"locale": "menu.profile"
},
{
"id": 29,
"parentId": 28,
"path": "/profile/basic",
"title": "基础详情页",
"name": "ProfileBasic",
"component": "/profile/basic/index",
"locale": "menu.profile.basic"
},
{
"id": 30,
"parentId": 26,
"path": "/list/search-list",
"title": "搜索列表",
"name": "SearchList",
"component": "/list/search-list",
"locale": "menu.list.search-list"
},
{
"id": 31,
"parentId": 30,
"path": "/list/search-list/articles",
"title": "搜索列表(文章)",
"name": "SearchListArticles",
"component": "/list/search-list/articles",
"locale": "menu.list.search-list.articles"
},
{
"id": 32,
"parentId": 30,
"path": "/list/search-list/projects",
"title": "搜索列表(项目)",
"name": "SearchListProjects",
"component": "/list/search-list/projects",
"locale": "menu.list.search-list.projects"
},
{
"id": 33,
"parentId": 30,
"path": "/list/search-list/applications",
"title": "搜索列表(应用)",
"name": "SearchListApplications",
"component": "/list/search-list/applications",
"locale": "menu.list.search-list.applications"
},
{
"id": 34,
"parentId": 26,
"path": "/list/basic-list",
"title": "标准列表",
"name": "BasicCard",
"component": "/list/basic-list",
"locale": "menu.list.basic-list"
},
{
"id": 35,
"parentId": 28,
"path": "/profile/advanced",
"title": "高级详细页",
"name": "ProfileAdvanced",
"component": "/profile/advanced/index",
"locale": "menu.profile.advanced"
},
{
"id": 4,
"parentId": 3,
"title": "基础表单",
"component": "/form/basic-form/index",
"path": "/form/basic-form",
"name": "FormBasic",
"keepAlive": false,
"locale": "menu.form.basic-form"
},
{
"id": 36,
"parentId": 0,
"title": "个人页",
"icon": "UserOutlined",
"component": "RouteView",
"redirect": "/account/center",
"path": "/account",
"name": "Account",
"locale": "menu.account"
},
{
"id": 37,
"parentId": 36,
"path": "/account/center",
"title": "个人中心",
"name": "AccountCenter",
"component": "/account/center",
"locale": "menu.account.center"
},
{
"id": 38,
"parentId": 36,
"path": "/account/settings",
"title": "个人设置",
"name": "AccountSettings",
"component": "/account/settings",
"locale": "menu.account.settings"
},
{
"id": 39,
"parentId": 3,
"title": "分步表单",
"component": "/form/step-form/index",
"path": "/form/step-form",
"name": "FormStep",
"keepAlive": false,
"locale": "menu.form.step-form"
},
{
"id": 40,
"parentId": 3,
"title": "高级表单",
"component": "/form/advanced-form/index",
"path": "/form/advanced-form",
"name": "FormAdvanced",
"keepAlive": false,
"locale": "menu.form.advanced-form"
},
{
"id": 41,
"parentId": 26,
"path": "/list/table-list",
"title": "查询表格",
"name": "ConsultTable",
"component": "/list/table-list",
"locale": "menu.list.consult-table"
},
{
"id": 42,
"parentId": 1,
"title": "监控页",
"component": "/dashboard/monitor",
"path": "/dashboard/monitor",
"name": "DashboardMonitor",
"keepAlive": true,
"locale": "menu.dashboard.monitor"
},
{
"id": 43,
"parentId": 1,
"title": "工作台",
"component": "/dashboard/workplace",
"path": "/dashboard/workplace",
"name": "DashboardWorkplace",
"keepAlive": true,
"locale": "menu.dashboard.workplace"
},
{
"id": 44,
"parentId": 26,
"path": "/list/crud-table",
"title": "增删改查表格",
"name": "CrudTable",
"component": "/list/crud-table",
"locale": "menu.list.crud-table"
},
{
"id": 45,
"parentId": 9,
"path": "/menu/menu4",
"redirect": "/menu/menu4/menu1",
"title": "菜单2-1",
"component": "RouteView",
"locale": "menu.menu.menu4"
},
{
"id": 46,
"parentId": 45,
"path": "/menu/menu4/menu1",
"component": "/menu/menu-2-1/menu1",
"title": "菜单2-1-1",
"keepAlive": true,
"locale": "menu.menu4.menu1"
},
{
"id": 47,
"parentId": 45,
"path": "/menu/menu4/menu2",
"component": "/menu/menu-2-1/menu2",
"title": "菜单2-1-2",
"keepAlive": true,
"locale": "menu.menu4.menu2"
}
]`

55
internal/server/task.go Normal file
View File

@ -0,0 +1,55 @@
package server
import (
"context"
"github.com/go-co-op/gocron"
"go.uber.org/zap"
"nunu-layout-admin/internal/task"
"nunu-layout-admin/pkg/log"
"time"
)
type TaskServer struct {
log *log.Logger
scheduler *gocron.Scheduler
userTask task.UserTask
}
func NewTaskServer(
log *log.Logger,
userTask task.UserTask,
) *TaskServer {
return &TaskServer{
log: log,
userTask: userTask,
}
}
func (t *TaskServer) Start(ctx context.Context) error {
gocron.SetPanicHandler(func(jobName string, recoverData interface{}) {
t.log.Error("TaskServer Panic", zap.String("job", jobName), zap.Any("recover", recoverData))
})
// eg: crontab task
t.scheduler = gocron.NewScheduler(time.UTC)
// if you are in China, you will need to change the time zone as follows
// t.scheduler = gocron.NewScheduler(time.FixedZone("PRC", 8*60*60))
//_, err := t.scheduler.Every("3s").Do(func()
_, err := t.scheduler.CronWithSeconds("0/3 * * * * *").Do(func() {
err := t.userTask.CheckUser(ctx)
if err != nil {
t.log.Error("CheckUser error", zap.Error(err))
}
})
if err != nil {
t.log.Error("CheckUser error", zap.Error(err))
}
t.scheduler.StartBlocking()
return nil
}
func (t *TaskServer) Stop(ctx context.Context) error {
t.scheduler.Stop()
t.log.Info("TaskServer stop...")
return nil
}

465
internal/service/admin.go Normal file
View File

@ -0,0 +1,465 @@
package service
import (
"context"
"errors"
"github.com/duke-git/lancet/v2/convertor"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
v1 "nunu-layout-admin/api/v1"
"nunu-layout-admin/internal/model"
"nunu-layout-admin/internal/repository"
"strings"
"time"
)
type AdminService interface {
Login(ctx context.Context, req *v1.LoginRequest) (string, error)
GetAdminUsers(ctx context.Context, req *v1.GetAdminUsersRequest) (*v1.GetAdminUsersResponseData, error)
GetAdminUser(ctx context.Context, uid uint) (*v1.GetAdminUserResponseData, error)
AdminUserUpdate(ctx context.Context, req *v1.AdminUserUpdateRequest) error
AdminUserCreate(ctx context.Context, req *v1.AdminUserCreateRequest) error
AdminUserDelete(ctx context.Context, id uint) error
GetUserPermissions(ctx context.Context, uid uint) (*v1.GetUserPermissionsData, error)
GetRolePermissions(ctx context.Context, role string) (*v1.GetRolePermissionsData, error)
UpdateRolePermission(ctx context.Context, req *v1.UpdateRolePermissionRequest) error
GetAdminMenus(ctx context.Context) (*v1.GetMenuResponseData, error)
GetMenus(ctx context.Context, uid uint) (*v1.GetMenuResponseData, error)
MenuUpdate(ctx context.Context, req *v1.MenuUpdateRequest) error
MenuCreate(ctx context.Context, req *v1.MenuCreateRequest) error
MenuDelete(ctx context.Context, id uint) error
GetRoles(ctx context.Context, req *v1.GetRoleListRequest) (*v1.GetRolesResponseData, error)
RoleUpdate(ctx context.Context, req *v1.RoleUpdateRequest) error
RoleCreate(ctx context.Context, req *v1.RoleCreateRequest) error
RoleDelete(ctx context.Context, id uint) error
GetApis(ctx context.Context, req *v1.GetApisRequest) (*v1.GetApisResponseData, error)
ApiUpdate(ctx context.Context, req *v1.ApiUpdateRequest) error
ApiCreate(ctx context.Context, req *v1.ApiCreateRequest) error
ApiDelete(ctx context.Context, id uint) error
}
func NewAdminService(
service *Service,
adminRepository repository.AdminRepository,
) AdminService {
return &adminService{
Service: service,
adminRepository: adminRepository,
}
}
type adminService struct {
*Service
adminRepository repository.AdminRepository
}
func (s *adminService) GetAdminUser(ctx context.Context, uid uint) (*v1.GetAdminUserResponseData, error) {
user, err := s.adminRepository.GetAdminUser(ctx, uid)
if err != nil {
return nil, err
}
roles, _ := s.adminRepository.GetUserRoles(ctx, uid)
return &v1.GetAdminUserResponseData{
Email: user.Email,
ID: user.ID,
Username: user.Username,
Nickname: user.Nickname,
Phone: user.Phone,
Roles: roles,
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"),
}, nil
}
func (s *adminService) Login(ctx context.Context, req *v1.LoginRequest) (string, error) {
user, err := s.adminRepository.GetAdminUserByUsername(ctx, req.Username)
if err != nil {
if err == gorm.ErrRecordNotFound {
return "", v1.ErrUnauthorized
}
return "", v1.ErrInternalServerError
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password))
if err != nil {
return "", err
}
token, err := s.jwt.GenToken(user.ID, time.Now().Add(time.Hour*24*90))
if err != nil {
return "", err
}
return token, nil
}
func (s *adminService) GetAdminUsers(ctx context.Context, req *v1.GetAdminUsersRequest) (*v1.GetAdminUsersResponseData, error) {
list, total, err := s.adminRepository.GetAdminUsers(ctx, req)
if err != nil {
return nil, err
}
data := &v1.GetAdminUsersResponseData{
List: make([]v1.AdminUserDataItem, 0),
Total: total,
}
for _, user := range list {
roles, err := s.adminRepository.GetUserRoles(ctx, user.ID)
if err != nil {
s.logger.Error("GetUserRoles error", zap.Error(err))
continue
}
data.List = append(data.List, v1.AdminUserDataItem{
Email: user.Email,
ID: user.ID,
Nickname: user.Nickname,
Username: user.Username,
Phone: user.Phone,
Roles: roles,
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"),
})
}
return data, nil
}
func (s *adminService) AdminUserUpdate(ctx context.Context, req *v1.AdminUserUpdateRequest) error {
old, _ := s.adminRepository.GetAdminUser(ctx, req.ID)
if req.Password != "" {
hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
req.Password = string(hash)
} else {
req.Password = old.Password
}
err := s.adminRepository.UpdateUserRoles(ctx, req.ID, req.Roles)
if err != nil {
return err
}
return s.adminRepository.AdminUserUpdate(ctx, &model.AdminUser{
Model: gorm.Model{
ID: req.ID,
},
Email: req.Email,
Nickname: req.Nickname,
Phone: req.Phone,
Username: req.Username,
})
}
func (s *adminService) AdminUserCreate(ctx context.Context, req *v1.AdminUserCreateRequest) error {
hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
req.Password = string(hash)
err = s.adminRepository.AdminUserCreate(ctx, &model.AdminUser{
Email: req.Email,
Nickname: req.Nickname,
Phone: req.Phone,
Username: req.Username,
Password: req.Password,
})
if err != nil {
return err
}
user, err := s.adminRepository.GetAdminUserByUsername(ctx, req.Username)
if err != nil {
return err
}
err = s.adminRepository.UpdateUserRoles(ctx, user.ID, req.Roles)
if err != nil {
return err
}
return err
}
func (s *adminService) AdminUserDelete(ctx context.Context, id uint) error {
// 删除用户角色
err := s.adminRepository.DeleteUserRoles(ctx, id)
if err != nil {
return err
}
return s.adminRepository.AdminUserDelete(ctx, id)
}
func (s *adminService) UpdateRolePermission(ctx context.Context, req *v1.UpdateRolePermissionRequest) error {
permissions := map[string]struct{}{}
for _, v := range req.List {
perm := strings.Split(v, model.PermSep)
if len(perm) == 2 {
permissions[v] = struct{}{}
}
}
return s.adminRepository.UpdateRolePermission(ctx, req.Role, permissions)
}
func (s *adminService) GetApis(ctx context.Context, req *v1.GetApisRequest) (*v1.GetApisResponseData, error) {
list, total, err := s.adminRepository.GetApis(ctx, req)
if err != nil {
return nil, err
}
groups, err := s.adminRepository.GetApiGroups(ctx)
if err != nil {
return nil, err
}
data := &v1.GetApisResponseData{
List: make([]v1.ApiDataItem, 0),
Total: total,
Groups: groups,
}
for _, api := range list {
data.List = append(data.List, v1.ApiDataItem{
CreatedAt: api.CreatedAt.Format("2006-01-02 15:04:05"),
Group: api.Group,
ID: api.ID,
Method: api.Method,
Name: api.Name,
Path: api.Path,
UpdatedAt: api.UpdatedAt.Format("2006-01-02 15:04:05"),
})
}
return data, nil
}
func (s *adminService) ApiUpdate(ctx context.Context, req *v1.ApiUpdateRequest) error {
return s.adminRepository.ApiUpdate(ctx, &model.Api{
Group: req.Group,
Method: req.Method,
Name: req.Name,
Path: req.Path,
Model: gorm.Model{
ID: req.ID,
},
})
}
func (s *adminService) ApiCreate(ctx context.Context, req *v1.ApiCreateRequest) error {
return s.adminRepository.ApiCreate(ctx, &model.Api{
Group: req.Group,
Method: req.Method,
Name: req.Name,
Path: req.Path,
})
}
func (s *adminService) ApiDelete(ctx context.Context, id uint) error {
return s.adminRepository.ApiDelete(ctx, id)
}
func (s *adminService) GetUserPermissions(ctx context.Context, uid uint) (*v1.GetUserPermissionsData, error) {
data := &v1.GetUserPermissionsData{
List: []string{},
}
list, err := s.adminRepository.GetUserPermissions(ctx, uid)
if err != nil {
return nil, err
}
for _, v := range list {
if len(v) == 3 {
data.List = append(data.List, strings.Join([]string{v[1], v[2]}, model.PermSep))
}
}
return data, nil
}
func (s *adminService) GetRolePermissions(ctx context.Context, role string) (*v1.GetRolePermissionsData, error) {
data := &v1.GetRolePermissionsData{
List: []string{},
}
list, err := s.adminRepository.GetRolePermissions(ctx, role)
if err != nil {
return nil, err
}
for _, v := range list {
if len(v) == 3 {
data.List = append(data.List, strings.Join([]string{v[1], v[2]}, model.PermSep))
}
}
return data, nil
}
func (s *adminService) MenuUpdate(ctx context.Context, req *v1.MenuUpdateRequest) error {
return s.adminRepository.MenuUpdate(ctx, &model.Menu{
Component: req.Component,
Icon: req.Icon,
KeepAlive: req.KeepAlive,
HideInMenu: req.HideInMenu,
Locale: req.Locale,
Weight: req.Weight,
Name: req.Name,
ParentID: req.ParentID,
Path: req.Path,
Redirect: req.Redirect,
Title: req.Title,
URL: req.URL,
Model: gorm.Model{
ID: req.ID,
},
})
}
func (s *adminService) MenuCreate(ctx context.Context, req *v1.MenuCreateRequest) error {
return s.adminRepository.MenuCreate(ctx, &model.Menu{
Component: req.Component,
Icon: req.Icon,
KeepAlive: req.KeepAlive,
HideInMenu: req.HideInMenu,
Locale: req.Locale,
Weight: req.Weight,
Name: req.Name,
ParentID: req.ParentID,
Path: req.Path,
Redirect: req.Redirect,
Title: req.Title,
URL: req.URL,
})
}
func (s *adminService) MenuDelete(ctx context.Context, id uint) error {
return s.adminRepository.MenuDelete(ctx, id)
}
func (s *adminService) GetMenus(ctx context.Context, uid uint) (*v1.GetMenuResponseData, error) {
menuList, err := s.adminRepository.GetMenuList(ctx)
if err != nil {
s.logger.WithContext(ctx).Error("GetMenuList error", zap.Error(err))
return nil, err
}
data := &v1.GetMenuResponseData{
List: make([]v1.MenuDataItem, 0),
}
// 获取权限的菜单
permissions, err := s.adminRepository.GetUserPermissions(ctx, uid)
if err != nil {
return nil, err
}
menuPermMap := map[string]struct{}{}
for _, permission := range permissions {
// 防呆设置,超管可以看到所有菜单
if convertor.ToString(uid) == model.AdminUserID {
menuPermMap[strings.TrimPrefix(permission[1], model.MenuResourcePrefix)] = struct{}{}
} else {
if len(permission) == 3 && strings.HasPrefix(permission[1], model.MenuResourcePrefix) {
menuPermMap[strings.TrimPrefix(permission[1], model.MenuResourcePrefix)] = struct{}{}
}
}
}
for _, menu := range menuList {
if _, ok := menuPermMap[menu.Path]; ok {
data.List = append(data.List, v1.MenuDataItem{
ID: menu.ID,
Name: menu.Name,
Title: menu.Title,
Path: menu.Path,
Component: menu.Component,
Redirect: menu.Redirect,
KeepAlive: menu.KeepAlive,
HideInMenu: menu.HideInMenu,
Locale: menu.Locale,
Weight: menu.Weight,
Icon: menu.Icon,
ParentID: menu.ParentID,
UpdatedAt: menu.UpdatedAt.Format("2006-01-02 15:04:05"),
URL: menu.URL,
})
}
}
return data, nil
}
func (s *adminService) GetAdminMenus(ctx context.Context) (*v1.GetMenuResponseData, error) {
menuList, err := s.adminRepository.GetMenuList(ctx)
if err != nil {
s.logger.WithContext(ctx).Error("GetMenuList error", zap.Error(err))
return nil, err
}
data := &v1.GetMenuResponseData{
List: make([]v1.MenuDataItem, 0),
}
for _, menu := range menuList {
data.List = append(data.List, v1.MenuDataItem{
ID: menu.ID,
Name: menu.Name,
Title: menu.Title,
Path: menu.Path,
Component: menu.Component,
Redirect: menu.Redirect,
KeepAlive: menu.KeepAlive,
HideInMenu: menu.HideInMenu,
Locale: menu.Locale,
Weight: menu.Weight,
Icon: menu.Icon,
ParentID: menu.ParentID,
UpdatedAt: menu.UpdatedAt.Format("2006-01-02 15:04:05"),
URL: menu.URL,
})
}
return data, nil
}
func (s *adminService) RoleUpdate(ctx context.Context, req *v1.RoleUpdateRequest) error {
return s.adminRepository.RoleUpdate(ctx, &model.Role{
Name: req.Name,
Sid: req.Sid,
Model: gorm.Model{
ID: req.ID,
},
})
}
func (s *adminService) RoleCreate(ctx context.Context, req *v1.RoleCreateRequest) error {
_, err := s.adminRepository.GetRoleBySid(ctx, req.Sid)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return s.adminRepository.RoleCreate(ctx, &model.Role{
Name: req.Name,
Sid: req.Sid,
})
} else {
return err
}
}
return nil
}
func (s *adminService) RoleDelete(ctx context.Context, id uint) error {
old, err := s.adminRepository.GetRole(ctx, id)
if err != nil {
return err
}
if err := s.adminRepository.CasbinRoleDelete(ctx, old.Sid); err != nil {
return err
}
return s.adminRepository.RoleDelete(ctx, id)
}
func (s *adminService) GetRoles(ctx context.Context, req *v1.GetRoleListRequest) (*v1.GetRolesResponseData, error) {
list, total, err := s.adminRepository.GetRoles(ctx, req)
if err != nil {
return nil, err
}
data := &v1.GetRolesResponseData{
List: make([]v1.RoleDataItem, 0),
Total: total,
}
for _, role := range list {
data.List = append(data.List, v1.RoleDataItem{
ID: role.ID,
Name: role.Name,
Sid: role.Sid,
UpdatedAt: role.UpdatedAt.Format("2006-01-02 15:04:05"),
CreatedAt: role.CreatedAt.Format("2006-01-02 15:04:05"),
})
}
return data, nil
}

View File

@ -0,0 +1,29 @@
package service
import (
"nunu-layout-admin/internal/repository"
"nunu-layout-admin/pkg/jwt"
"nunu-layout-admin/pkg/log"
"nunu-layout-admin/pkg/sid"
)
type Service struct {
logger *log.Logger
sid *sid.Sid
jwt *jwt.JWT
tm repository.Transaction
}
func NewService(
tm repository.Transaction,
logger *log.Logger,
sid *sid.Sid,
jwt *jwt.JWT,
) *Service {
return &Service{
logger: logger,
sid: sid,
jwt: jwt,
tm: tm,
}
}

30
internal/service/user.go Normal file
View File

@ -0,0 +1,30 @@
package service
import (
"context"
"nunu-layout-admin/internal/model"
"nunu-layout-admin/internal/repository"
)
type UserService interface {
GetUser(ctx context.Context, id int64) (*model.User, error)
}
func NewUserService(
service *Service,
userRepository repository.UserRepository,
) UserService {
return &userService{
Service: service,
userRepository: userRepository,
}
}
type userService struct {
*Service
userRepository repository.UserRepository
}
func (s *userService) GetUser(ctx context.Context, id int64) (*model.User, error) {
return s.userRepository.GetUser(ctx, id)
}

27
internal/task/task.go Normal file
View File

@ -0,0 +1,27 @@
package task
import (
"nunu-layout-admin/internal/repository"
"nunu-layout-admin/pkg/jwt"
"nunu-layout-admin/pkg/log"
"nunu-layout-admin/pkg/sid"
)
type Task struct {
logger *log.Logger
sid *sid.Sid
jwt *jwt.JWT
tm repository.Transaction
}
func NewTask(
tm repository.Transaction,
logger *log.Logger,
sid *sid.Sid,
) *Task {
return &Task{
logger: logger,
sid: sid,
tm: tm,
}
}

31
internal/task/user.go Normal file
View File

@ -0,0 +1,31 @@
package task
import (
"context"
"nunu-layout-admin/internal/repository"
)
type UserTask interface {
CheckUser(ctx context.Context) error
}
func NewUserTask(
task *Task,
userRepo repository.UserRepository,
) UserTask {
return &userTask{
userRepo: userRepo,
Task: task,
}
}
type userTask struct {
userRepo repository.UserRepository
*Task
}
func (t userTask) CheckUser(ctx context.Context) error {
// do something
t.logger.Info("CheckUser")
return nil
}

74
pkg/app/app.go Normal file
View File

@ -0,0 +1,74 @@
package app
import (
"context"
"log"
"nunu-layout-admin/pkg/server"
"os"
"os/signal"
"syscall"
)
type App struct {
name string
servers []server.Server
}
type Option func(a *App)
func NewApp(opts ...Option) *App {
a := &App{}
for _, opt := range opts {
opt(a)
}
return a
}
func WithServer(servers ...server.Server) Option {
return func(a *App) {
a.servers = servers
}
}
func WithName(name string) Option {
return func(a *App) {
a.name = name
}
}
func (a *App) Run(ctx context.Context) error {
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
defer cancel()
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
for _, srv := range a.servers {
go func(srv server.Server) {
err := srv.Start(ctx)
if err != nil {
log.Printf("Server start err: %v", err)
}
}(srv)
}
select {
case <-signals:
// Received termination signal
log.Println("Received termination signal")
case <-ctx.Done():
// Context canceled
log.Println("Context canceled")
}
// Gracefully stop the servers
for _, srv := range a.servers {
err := srv.Stop(ctx)
if err != nil {
log.Printf("Server stop err: %v", err)
}
}
return nil
}

26
pkg/config/config.go Normal file
View File

@ -0,0 +1,26 @@
package config
import (
"fmt"
"github.com/spf13/viper"
"os"
)
func NewConfig(p string) *viper.Viper {
envConf := os.Getenv("APP_CONF")
if envConf == "" {
envConf = p
}
fmt.Println("load conf file:", envConf)
return getConfig(envConf)
}
func getConfig(path string) *viper.Viper {
conf := viper.New()
conf.SetConfigFile(path)
err := conf.ReadInConfig()
if err != nil {
panic(err)
}
return conf
}

63
pkg/jwt/jwt.go Normal file
View File

@ -0,0 +1,63 @@
package jwt
import (
"errors"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/spf13/viper"
)
type JWT struct {
key []byte
}
type MyCustomClaims struct {
UserId uint
jwt.RegisteredClaims
}
func NewJwt(conf *viper.Viper) *JWT {
return &JWT{key: []byte(conf.GetString("security.jwt.key"))}
}
func (j *JWT) GenToken(userId uint, expiresAt time.Time) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, MyCustomClaims{
UserId: userId,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expiresAt),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "",
Subject: "",
ID: "",
Audience: []string{},
},
})
// Sign and get the complete encoded token as a string using the key
tokenString, err := token.SignedString(j.key)
if err != nil {
return "", err
}
return tokenString, nil
}
func (j *JWT) ParseToken(tokenString string) (*MyCustomClaims, error) {
tokenString = strings.TrimPrefix(tokenString, "Bearer ")
if strings.TrimSpace(tokenString) == "" {
return nil, errors.New("token is empty")
}
token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return j.key, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
return claims, nil
} else {
return nil, err
}
}

114
pkg/log/log.go Normal file
View File

@ -0,0 +1,114 @@
package log
import (
"context"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
"os"
"time"
)
const ctxLoggerKey = "zapLogger"
type Logger struct {
*zap.Logger
}
func NewLog(conf *viper.Viper) *Logger {
// log address "out.log" User-defined
lp := conf.GetString("log.log_file_name")
lv := conf.GetString("log.log_level")
var level zapcore.Level
//debug<info<warn<error<fatal<panic
switch lv {
case "debug":
level = zap.DebugLevel
case "info":
level = zap.InfoLevel
case "warn":
level = zap.WarnLevel
case "error":
level = zap.ErrorLevel
default:
level = zap.InfoLevel
}
hook := lumberjack.Logger{
Filename: lp, // Log file path
MaxSize: conf.GetInt("log.max_size"), // Maximum size unit for each log file: M
MaxBackups: conf.GetInt("log.max_backups"), // The maximum number of backups that can be saved for log files
MaxAge: conf.GetInt("log.max_age"), // Maximum number of days the file can be saved
Compress: conf.GetBool("log.compress"), // Compression or not
}
var encoder zapcore.Encoder
if conf.GetString("log.encoding") == "console" {
encoder = zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "Logger",
CallerKey: "caller",
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseColorLevelEncoder,
EncodeTime: timeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.FullCallerEncoder,
})
} else {
encoder = zapcore.NewJSONEncoder(zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.EpochTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
})
}
core := zapcore.NewCore(
encoder,
zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&hook)), // Print to console and file
level,
)
if conf.GetString("env") != "prod" {
return &Logger{zap.New(core, zap.Development(), zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))}
}
return &Logger{zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))}
}
func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
//enc.AppendString(t.Format("2006-01-02 15:04:05"))
enc.AppendString(t.Format("2006-01-02 15:04:05.000000000"))
}
// WithValue Adds a field to the specified context
func (l *Logger) WithValue(ctx context.Context, fields ...zapcore.Field) context.Context {
if c, ok := ctx.(*gin.Context); ok {
ctx = c.Request.Context()
c.Request = c.Request.WithContext(context.WithValue(ctx, ctxLoggerKey, l.WithContext(ctx).With(fields...)))
return c
}
return context.WithValue(ctx, ctxLoggerKey, l.WithContext(ctx).With(fields...))
}
// WithContext Returns a zap instance from the specified context
func (l *Logger) WithContext(ctx context.Context) *Logger {
if c, ok := ctx.(*gin.Context); ok {
ctx = c.Request.Context()
}
zl := ctx.Value(ctxLoggerKey)
ctxLogger, ok := zl.(*zap.Logger)
if ok {
return &Logger{ctxLogger}
}
return l
}

61
pkg/server/grpc/grpc.go Normal file
View File

@ -0,0 +1,61 @@
package grpc
import (
"context"
"fmt"
"google.golang.org/grpc"
"net"
"nunu-layout-admin/pkg/log"
"time"
)
type Server struct {
*grpc.Server
host string
port int
logger *log.Logger
}
type Option func(s *Server)
func NewServer(logger *log.Logger, opts ...Option) *Server {
s := &Server{
Server: grpc.NewServer(),
logger: logger,
}
for _, opt := range opts {
opt(s)
}
return s
}
func WithServerHost(host string) Option {
return func(s *Server) {
s.host = host
}
}
func WithServerPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func (s *Server) Start(ctx context.Context) error {
lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.host, s.port))
if err != nil {
s.logger.Sugar().Fatalf("Failed to listen: %v", err)
}
if err = s.Server.Serve(lis); err != nil {
s.logger.Sugar().Fatalf("Failed to serve: %v", err)
}
return nil
}
func (s *Server) Stop(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
s.Server.GracefulStop()
s.logger.Info("Server exiting")
return nil
}

68
pkg/server/http/http.go Normal file
View File

@ -0,0 +1,68 @@
package http
import (
"context"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"nunu-layout-admin/pkg/log"
"time"
)
type Server struct {
*gin.Engine
httpSrv *http.Server
host string
port int
logger *log.Logger
}
type Option func(s *Server)
func NewServer(engine *gin.Engine, logger *log.Logger, opts ...Option) *Server {
s := &Server{
Engine: engine,
logger: logger,
}
for _, opt := range opts {
opt(s)
}
return s
}
func WithServerHost(host string) Option {
return func(s *Server) {
s.host = host
}
}
func WithServerPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func (s *Server) Start(ctx context.Context) error {
s.httpSrv = &http.Server{
Addr: fmt.Sprintf("%s:%d", s.host, s.port),
Handler: s,
}
if err := s.httpSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
s.logger.Sugar().Fatalf("listen: %s\n", err)
}
return nil
}
func (s *Server) Stop(ctx context.Context) error {
s.logger.Sugar().Info("Shutting down server...")
// The context is used to inform the server it has 5 seconds to finish
// the request it is currently handling
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := s.httpSrv.Shutdown(ctx); err != nil {
s.logger.Sugar().Fatal("Server forced to shutdown: ", err)
}
s.logger.Sugar().Info("Server exiting")
return nil
}

16
pkg/server/server.go Normal file
View File

@ -0,0 +1,16 @@
package server
import (
"context"
"net/url"
)
type Server interface {
Start(context.Context) error
Stop(context.Context) error
}
// Endpointer is registry endpoint.
type Endpointer interface {
Endpoint() (*url.URL, error)
}

24
pkg/sid/convert.go Normal file
View File

@ -0,0 +1,24 @@
package sid
const (
base62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)
func IntToBase62(n int) string {
if n == 0 {
return string(base62[0])
}
var result []byte
for n > 0 {
result = append(result, base62[n%62])
n /= 62
}
// 反转字符串
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
result[i], result[j] = result[j], result[i]
}
return string(result)
}

27
pkg/sid/sid.go Normal file
View File

@ -0,0 +1,27 @@
package sid
import (
"github.com/sony/sonyflake"
)
type Sid struct {
sf *sonyflake.Sonyflake
}
func NewSid() *Sid {
sf := sonyflake.NewSonyflake(sonyflake.Settings{})
if sf == nil {
panic("sonyflake not created")
}
return &Sid{sf}
}
func (s Sid) GenString() (string, error) {
id, err := s.sf.NextID()
if err != nil {
return "", err
}
return IntToBase62(int(id)), nil
}
func (s Sid) GenUint64() (uint64, error) {
return s.sf.NextID()
}

128
pkg/zapgorm2/zapgorm2.go Normal file
View File

@ -0,0 +1,128 @@
package zapgorm2
import (
"context"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"path/filepath"
"runtime"
"strings"
"time"
"go.uber.org/zap"
gormlogger "gorm.io/gorm/logger"
)
const ctxLoggerKey = "zapLogger"
type Logger struct {
ZapLogger *zap.Logger
SlowThreshold time.Duration
Colorful bool
IgnoreRecordNotFoundError bool
ParameterizedQueries bool
LogLevel gormlogger.LogLevel
}
func New(zapLogger *zap.Logger) gormlogger.Interface {
return &Logger{
ZapLogger: zapLogger,
LogLevel: gormlogger.Warn,
SlowThreshold: 100 * time.Millisecond,
Colorful: false,
IgnoreRecordNotFoundError: false,
ParameterizedQueries: false,
}
}
func (l *Logger) LogMode(level gormlogger.LogLevel) gormlogger.Interface {
newlogger := *l
newlogger.LogLevel = level
return &newlogger
}
// Info print info
func (l Logger) Info(ctx context.Context, msg string, data ...interface{}) {
if l.LogLevel >= gormlogger.Info {
l.logger(ctx).Sugar().Infof(msg, data...)
}
}
// Warn print warn messages
func (l Logger) Warn(ctx context.Context, msg string, data ...interface{}) {
if l.LogLevel >= gormlogger.Warn {
l.logger(ctx).Sugar().Warnf(msg, data...)
}
}
// Error print error messages
func (l Logger) Error(ctx context.Context, msg string, data ...interface{}) {
if l.LogLevel >= gormlogger.Error {
l.logger(ctx).Sugar().Errorf(msg, data...)
}
}
func (l Logger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
if l.LogLevel <= gormlogger.Silent {
return
}
elapsed := time.Since(begin)
elapsedStr := fmt.Sprintf("%.3fms", float64(elapsed.Nanoseconds())/1e6)
logger := l.logger(ctx)
switch {
case err != nil && l.LogLevel >= gormlogger.Error && (!errors.Is(err, gormlogger.ErrRecordNotFound) || !l.IgnoreRecordNotFoundError):
sql, rows := fc()
if rows == -1 {
logger.Error("trace", zap.Error(err), zap.String("elapsed", elapsedStr), zap.Int64("rows", rows), zap.String("sql", sql))
} else {
logger.Error("trace", zap.Error(err), zap.String("elapsed", elapsedStr), zap.Int64("rows", rows), zap.String("sql", sql))
}
case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= gormlogger.Warn:
sql, rows := fc()
slowLog := fmt.Sprintf("SLOW SQL >= %v", l.SlowThreshold)
if rows == -1 {
logger.Warn("trace", zap.String("slow", slowLog), zap.String("elapsed", elapsedStr), zap.Int64("rows", rows), zap.String("sql", sql))
} else {
logger.Warn("trace", zap.String("slow", slowLog), zap.String("elapsed", elapsedStr), zap.Int64("rows", rows), zap.String("sql", sql))
}
case l.LogLevel == gormlogger.Info:
sql, rows := fc()
if rows == -1 {
logger.Info("trace", zap.String("elapsed", elapsedStr), zap.Int64("rows", rows), zap.String("sql", sql))
} else {
logger.Info("trace", zap.String("elapsed", elapsedStr), zap.Int64("rows", rows), zap.String("sql", sql))
}
}
}
var (
gormPackage = filepath.Join("gorm.io", "gorm")
)
func (l Logger) logger(ctx context.Context) *zap.Logger {
logger := l.ZapLogger
if ctx != nil {
if c, ok := ctx.(*gin.Context); ok {
ctx = c.Request.Context()
}
zl := ctx.Value(ctxLoggerKey)
ctxLogger, ok := zl.(*zap.Logger)
if ok {
logger = ctxLogger
}
}
for i := 2; i < 15; i++ {
_, file, _, ok := runtime.Caller(i)
switch {
case !ok:
case strings.HasSuffix(file, "_test.go"):
case strings.Contains(file, gormPackage):
default:
return logger.WithOptions(zap.AddCallerSkip(i - 1))
}
}
return logger
}

11
scripts/README.md Normal file
View File

@ -0,0 +1,11 @@
# `/scripts`
Scripts to perform various build, install, analysis, etc operations.
These scripts keep the root level Makefile small and simple.
Examples:
* https://github.com/kubernetes/helm/tree/master/scripts
* https://github.com/cockroachdb/cockroach/tree/master/scripts
* https://github.com/hashicorp/terraform/tree/master/scripts

BIN
storage/nunu-test.db Normal file

Binary file not shown.

23
web/.commitlintrc Normal file
View File

@ -0,0 +1,23 @@
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"type-enum": [
2,
"always",
[
"init",
"build",
"ci",
"chore",
"docs",
"feat",
"fix",
"perf",
"refactor",
"revert",
"style",
"test"
]
]
}
}

4
web/.env Normal file
View File

@ -0,0 +1,4 @@
VITE_APP_NAME=N-Admin
VITE_APP_BASE=/
VITE_APP_BASE_API=/api
VITE_APP_LOAD_ROUTE_WAY=BACKEND

12
web/.env.development Normal file
View File

@ -0,0 +1,12 @@
VITE_APP_BASE_API=http://127.0.0.1:8000
VITE_APP_BASE_URL=http://127.0.0.1:8000
# https://docs.antdv-pro.com/guide/server.html
# The following api is requested when the request parameter includes customDev
VITE_APP_BASE_API_DEV=/dev-api
VITE_APP_BASE_URL_DEV=http://127.0.0.1:6678/api
VITE_APP_LOAD_ROUTE_WAY=BACKEND
# The title of your application (string)
VITE_GLOB_APP_TITLE="N-Admin"

4
web/.env.production Normal file
View File

@ -0,0 +1,4 @@
VITE_APP_BASE_API=http://127.0.0.1:8000
VITE_APP_BASE_URL=http://127.0.0.1:8000
# The title of your application (string)
VITE_GLOB_APP_TITLE="N-admin"

24
web/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
.vscode
#dist

4
web/.npmrc Normal file
View File

@ -0,0 +1,4 @@
public-hoist-pattern[]=@vue/runtime-core
public-hoist-pattern[]=eslint-*
public-hoist-pattern[]=@typescript-eslint*
package-manager-strict=false

1119
web/CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

8
web/Dockerfile Normal file
View File

@ -0,0 +1,8 @@
FROM nginx
RUN rm /etc/nginx/conf.d/default.conf
ADD default.conf /etc/nginx/conf.d/default.conf
COPY dist/ /usr/share/nginx/html/
RUN chmod 775 -R /usr/share/nginx/html
EXPOSE 80/tcp

21
web/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) [2023] [Antdv Pro]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

102
web/README.md Normal file
View File

@ -0,0 +1,102 @@
<div align="center"> <a href="https://github.com/antdv-pro/antdv-pro"> <img alt="VbenAdmin Logo" width="200" height="200" src="./public/logo.svg"> </a> <br> <br>
<h1>Antdv Pro</h1>
</div>
![gitee](https://gitee.com/antdv-pro/antdv-pro/badge/star.svg)
![github](https://img.shields.io/github/stars/antdv-pro/antdv-pro?style=social)
**English** | [简体中文](./README.zh-CN.md)
## Introduction
AntdvPro is a complete set of enterprise-level mid-backend front-end/design solutions based on Vue3, Vite4, ant-design-vue4, Pinia, UnoCSS and Typescript. It refers to the design pattern of Ali react version antd-pro, using the latest and most popular The front-end technology stack has built-in dynamic routing, multi-theme, multi-layout and other functions, which can help you quickly build enterprise-level mid-background product prototypes.
## Features
* pnpm: Using the latest pnpm as a package management tool, it can greatly reduce the size of node_modules, speed up the installation speed of packages, and can also share dependencies to reduce disk usage.
* vite: vite as a front-end development tool, it can greatly speed up the start-up speed of the project, and also supports hot updates, which can greatly improve development efficiency.
* vue3: vue3.3.x as the front-end framework, the basic code is written in script-setup, with less code and low maintenance cost.
* nitro mock: Use nitro as the server's mock data, decoupled from the project, and more flexible and easy to use.
* ant-design-vue4: ant-design-vue4 as the UI framework, the author of admin-pro is also a core member of ant-design-vue, which can provide long-term maintenance support.
* pinia: pinia as a state management tool, it can greatly improve the readability and maintainability of the code, and also supports Typescript.
* UnoCSS: Atomic CSS framework, reduce the troubles caused by thinking about some common class names, and improve our development efficiency.
* Code specification: We have encapsulated a set of eslint-based code specification configuration files, which can be used out of the box to unify the problems brought by different teams.
* Theme: The design specifications of antd-pro of the react version are used, and a set of theme modes based on vue are developed. On this basis, some new functions are added to meet various different needs as much as possible.
* Request function: Based on axios, a set of request functions with complete types and some basic interceptor encapsulations are encapsulated. You only need to make corresponding implementation adjustments according to the requirements to meet the different needs of various projects.
* Mobile compatibility: We have tried our best to make the basic framework compatible with the mobile terminal mode, but because our main goal is the enterprise-level mid-background product, we have not made too much adaptation to the mobile terminal. If your project needs to adapt to the mobile terminal, you can refer to our code for corresponding adjustments.
## Preview
[antdv-pro](https://antdv-pro.com) - Test Account: admin/admin
[antdv-pro-docs](https://docs.antdv-pro.com) - Documentation
## Community
QQ Group: apply wechat group
Wechat: aibayanyu2022
Discord: [discord](https://discord.gg/tPb4G6gXmm)
WeChatGroup: apply wechat group to add author wechat
## Useage
```bash
# Install degit
npm i -g degit
# Pull the code
degit antdv-pro/antdv-pro [your project name]
# Switch to the project directory
cd [your project name]
# Install
pnpm install
# Development
pnpm dev
```
## Contribute
We are very welcome to have you participate in our open source project.
**Pull Request:**
1. Fork code!
2. Create your own branch: `git checkout -b feat-xxxx`
3. Submit your changes: `git commit -am 'feat(function): add xxxxx'`
4. Push your branch: `git push origin feat-xxxx`
5. submit`pull request`
Thank you to all the people who already contributed to antdv-pro!
<a href="https://github.com/antdv-pro/antdv-pro/graphs/contributors">
<img src="https://contrib.rocks/image?repo=antdv-pro/antdv-pro&max=100&columns=15" />
</a>
## Support
If you like our project, you can support us by clicking the "Star" button in the upper right corner. Your support is my motivation. Thank you ~
Thanks to the open source project license provided by [Jetbrains](https://www.jetbrains.com/?from=antdv-pro).
## Sponsor
If you like our project, you can sponsor us to help us maintain the project better.
[Alipay/Wechat](https://docs.antdv-pro.com/other/sponsor.html)

103
web/README.zh-CN.md Normal file
View File

@ -0,0 +1,103 @@
<div align="center"> <a href="https://github.com/antdv-pro/antdv-pro"> <img alt="VbenAdmin Logo" width="200" height="200" src="./public/logo.svg"> </a> <br> <br>
<h1>Antdv Pro</h1>
</div>
![gitee](https://gitee.com/antdv-pro/antdv-pro/badge/star.svg)
![github](https://img.shields.io/github/stars/antdv-pro/antdv-pro?style=social)
[English](./README.md) | **简体中文**
## 介绍
AntdvPro是一个基于Vue3、Vite4、ant-design-vue4、Pinia、UnoCSS和Typescript的一整套企业级中后台前端/设计解决方案它参考了阿里react版本antd-pro的设计模式使用了最新最流行的前端技术栈内置了动态路由、多主题、多布局等功能可以帮助你快速搭建企业级中后台产品原型。
## 特性
* pnpm使用了最新的pnpm作为包管理工具它可以大大减少node_modules的体积加快包的安装速度同时还可以共享依赖减少磁盘占用。
* vitevite作为前端开发工具它可以大大加快项目的启动速度同时还支持热更新可以大大提高开发效率。
* vue3vue3.3.x作为前端框架基础代码全部使用script-setup的写法代码量少维护成本低。
* nitro mock采用nitro作为服务端的mock数据从工程中解耦处理更加灵活易用。
* ant-design-vue4ant-design-vue4作为UI框架admin-pro的作者也是ant-design-vue的核心成员可提供长期的维护支持。
* piniapinia作为状态管理工具它可以大大提高代码的可读性和可维护性同时还支持Typescript。
* UnoCSS原子化的CSS框架减少我们去想一些通用类名带来的烦恼提升我们的开发效率。
* 代码规范我们封装了一套基于eslint的代码规范配置文件开箱即用统一不同团队所带来的问题。
* 主题延用了react版本的antd-pro的设计规范开发了一套基于vue的主题模式在此基础上增加了一些新的功能尽可能的满足各种不同的需求。
* 请求函数基于axios封装了一套具有完善类型的请求函数以及一些基础的拦截器的封装只需要按照需求做对应的实现调整就能满足各种项目带来的不一样的需求。
* 移动端兼容:基础框架部分我们尽可能的对移动端的模式进行了兼容处理,但是由于我们的主要目标是企业级中后台产品,所以我们并没有对移动端做过多的适配,如果你的项目需要移动端的适配,可以参考我们的代码进行相应的调整。
## 演示
[antdv-pro](https://antdv-pro.com) - 测试账号admin/admin
[antdv-pro-docs](https://docs.antdv-pro.com) - 在线文档地址
## 社区
QQ群: 申请微信群
微信: [aibayanyu2022](https://u.wechat.com/MASIsAa8353Hi4e59-aBPaA)
Discord: [discord](https://discord.gg/tPb4G6gXmm)
微信群: 申请微信群加作者微信
## 使用
```bash
# 安装degit
npm i -g degit
# 拉取代码
degit antdv-pro/antdv-pro [your project name]
# 切换到项目目录
cd [your project name]
# 安装依赖
pnpm install
# 启动项目
pnpm dev
```
## 贡献
非常欢迎您参与到我们的开源项目中来~
**PR流程**
1. Fork 代码!
2. 创建自己的分支: `git checkout -b feat-xxxx`
3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
4. 推送您的分支: `git push origin feat-xxxx`
5. 提交`pull request`
感谢所有为`antdv-pro`做出贡献的小伙伴儿们!
<a href="https://github.com/antdv-pro/antdv-pro/graphs/contributors">
<img src="https://contrib.rocks/image?repo=antdv-pro/antdv-pro&max=100&columns=15" />
</a>
## 支持
如果你觉得这个项目对你有帮助,你可以点右上角 "Star" 支持一下,你的支持就是我的动力,谢谢~
感谢[Jetbrains](https://www.jetbrains.com/?from=antdv-pro).提供的开源项目许可证支持
## 赞助
如果你觉得这个项目对你有帮助,你可以点击下方链接对我进行赞助,谢谢~
[赞助](https://docs.antdv-pro.com/other/sponsor.html)

27
web/default.conf Normal file
View File

@ -0,0 +1,27 @@
server {
listen 80;
server_name _;
# gzip config
gzip on;
gzip_min_length 1k;
gzip_comp_level 9;
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
root /usr/share/nginx/html;
include /etc/nginx/mime.types;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
# rewrite ^/api(.*)$ $1 break;
proxy_pass https://api.antdv-pro.com;
client_max_body_size 100M;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

1
web/dist/_app.config.js vendored Normal file
View File

@ -0,0 +1 @@
window.__PRODUCTION__N__ADMIN__CONF__={"VITE_GLOB_APP_TITLE":"N-admin"};Object.freeze(window.__PRODUCTION__N__ADMIN__CONF__);Object.defineProperty(window, "__PRODUCTION__N__ADMIN__CONF__", {configurable: false,writable: false,});

1
web/dist/assets/401-C6PeSXhU.js vendored Normal file
View File

@ -0,0 +1 @@
import{a9 as n,aa as e,a2 as c,k as u,G as _,ac as p}from"./vue-Dl1fzmsf.js";import{a6 as l,H as i}from"./antd-vtmm7CAy.js";const k={__name:"401",setup(m){const a=p();function o(){a.replace({path:"/login"})}return(f,t)=>{const s=i,r=l;return c(),n(r,{status:"404",title:"401","sub-title":"登录已过期,请重新登陆"},{extra:e(()=>[u(s,{type:"primary",onClick:o},{default:e(()=>t[0]||(t[0]=[_(" 跳转登录 ")])),_:1})]),_:1})}}};export{k as default};

1
web/dist/assets/403-B6htc2SF.js vendored Normal file
View File

@ -0,0 +1 @@
import{ac as c,a2 as n,a9 as u,aa as a,k as p,G as _}from"./vue-Dl1fzmsf.js";import{H as i,a6 as l}from"./antd-vtmm7CAy.js";const x={__name:"403",setup(m){const e=c();function o(){e.replace({path:"/"})}return(f,t)=>{const s=i,r=l;return n(),u(r,{status:"403",title:"403","sub-title":"Sorry, you don't have access to this page."},{extra:a(()=>[p(s,{type:"primary",onClick:o},{default:a(()=>t[0]||(t[0]=[_(" Back to home ")])),_:1})]),_:1})}}};export{x as default};

1
web/dist/assets/404-BF6vYG99.js vendored Normal file
View File

@ -0,0 +1 @@
import{ac as n,a2 as c,a9 as u,aa as e,k as p,G as _}from"./vue-Dl1fzmsf.js";import{H as i,a6 as l}from"./antd-vtmm7CAy.js";const x={__name:"404",setup(m){const a=n();function o(){a.replace({path:"/"})}return(d,t)=>{const s=i,r=l;return c(),u(r,{status:"404",title:"404","sub-title":"Sorry, the page you visited does not exist."},{extra:e(()=>[p(s,{type:"primary",onClick:o},{default:e(()=>t[0]||(t[0]=[_(" Back Home ")])),_:1})]),_:1})}}};export{x as default};

1
web/dist/assets/500-3ZsQY5rx.js vendored Normal file
View File

@ -0,0 +1 @@
import{ac as n,a2 as c,a9 as u,aa as e,k as p,G as _}from"./vue-Dl1fzmsf.js";import{H as i,a6 as l}from"./antd-vtmm7CAy.js";const d={__name:"500",setup(m){const a=n();function r(){a.replace({path:"/"})}return(f,t)=>{const o=i,s=l;return c(),u(s,{status:"500",title:"500","sub-title":"Sorry, the server is reporting an error."},{extra:e(()=>[p(o,{type:"primary",onClick:r},{default:e(()=>t[0]||(t[0]=[_(" Back Home ")])),_:1})]),_:1})}}};export{d as default};

View File

@ -0,0 +1 @@
.activeChart[data-v-c858f221]{position:relative}.activeChartGrid p[data-v-c858f221]{position:absolute;top:80px}.activeChartGrid p[data-v-c858f221]:last-child{top:115px}.activeChartLegend[data-v-c858f221]{position:relative;height:20px;margin-top:8px;font-size:0;line-height:20px}.activeChartLegend span[data-v-c858f221]{display:inline-block;width:33.33%;font-size:12px;text-align:center}.activeChartLegend span[data-v-c858f221]:first-child{text-align:left}.activeChartLegend span[data-v-c858f221]:last-child{text-align:right}.dashedLine[data-v-c858f221]{position:relative;top:-70px;left:-3px;height:1px}.dashedLine .line[data-v-c858f221]{position:absolute;top:0;left:0;width:100%;height:100%;background-image:linear-gradient(to right,transparent 50%,#e9e9e9 50%);background-size:6px}.dashedLine[data-v-c858f221]:last-child{top:-36px}

View File

@ -0,0 +1 @@
import{T as y}from"./index-sUMRYBhU.js";import{_ as D}from"./index-C-JhWVfG.js";import{aD as M}from"./antd-vtmm7CAy.js";import{f as u,o as C,j as A,a2 as T,a3 as k,k as B,a4 as t,ad as r,u as n}from"./vue-Dl1fzmsf.js";import"./vec2-4Cx-bOHg.js";const w={class:"activeChart"},F={style:{marginTop:"32px"}},L={class:"activeChartGrid"},q={class:"activeChartLegend"},N={__name:"active-chart",setup(S){const e=u([]),i=u([]);let o,l,c;function g(a){return a<10?`0${a}`:a}function d(){e.value=[],i.value=[];for(let a=0;a<24;a+=1)e.value.push({x:`${g(a)}:00`,y:Math.floor(Math.random()*200)+a*50}),i.value.push(Math.floor(Math.random()*200)+a*50);o==null||o.changeData(i.value)}function f(){l=requestAnimationFrame(()=>{c=window.setTimeout(()=>{d(),f()},1e3)})}const m=u();return C(()=>{o=new y(m.value,{height:84,data:i.value,smooth:!0,autoFit:!0}),o.render(),f(),d()}),A(()=>{clearTimeout(c),l&&cancelAnimationFrame(l),o==null||o.destroy(),o=void 0}),(a,s)=>{var v,p,h,_;const x=M;return T(),k("div",w,[B(x,{title:"目标评估",value:"有望达到预期"}),t("div",F,[t("div",{ref_key:"tinyAreaContainer",ref:m},null,512)]),t("div",null,[t("div",L,[t("p",null,r(((v=[...n(e)].sort()[n(e).length-1])==null?void 0:v.y)+200)+" 亿元",1),t("p",null,r((p=[...n(e)].sort()[Math.floor(n(e).length/2)])==null?void 0:p.y)+" 亿元",1)]),s[0]||(s[0]=t("div",{class:"dashedLine"},[t("div",{class:"line"})],-1)),s[1]||(s[1]=t("div",{class:"dashedLine"},[t("div",{class:"line"})],-1))]),t("div",q,[s[2]||(s[2]=t("span",null,"00:00",-1)),t("span",null,r((h=n(e)[Math.floor(n(e).length/2)])==null?void 0:h.x),1),t("span",null,r((_=n(e)[n(e).length-1])==null?void 0:_.x),1)])])}}},I=D(N,[["__scopeId","data-v-c858f221"]]);export{I as default};

1
web/dist/assets/admin-Nl2XFEZr.js vendored Normal file

File diff suppressed because one or more lines are too long

1
web/dist/assets/admin-x2Ewtnku.js vendored Normal file
View File

@ -0,0 +1 @@
import{D as i,j as t,E as n,F as r}from"./index-C-JhWVfG.js";function s(e){return i("/v1/admin/roles",e)}function o(e){return t("/v1/admin/role",e)}function u(e){return n("/v1/admin/role",e)}function p(e){return r("/v1/admin/role",e)}function d(e){return i("/v1/admin/role/permissions",e)}function m(e){return n("/v1/admin/role/permission",e)}function A(e){return i("/v1/admin/apis",e)}function l(e){return t("/v1/admin/api",e)}function c(e){return n("/v1/admin/api",e)}function f(e){return r("/v1/admin/api",e)}export{A as a,d as b,l as c,f as d,p as e,u as f,s as g,o as h,m as i,c as u};

444
web/dist/assets/antd-vtmm7CAy.js vendored Normal file

File diff suppressed because one or more lines are too long

1
web/dist/assets/api-DDa9fDH4.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
web/dist/assets/articles-Bba17joW.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.scrollbar[data-v-24f2c992]::-webkit-scrollbar{width:5px;height:10px}.scrollbar[data-v-24f2c992]::-webkit-scrollbar-thumb{border-radius:5px;-webkit-box-shadow:inset 0 0 5px rgba(0,0,0,.2);background:#bebebe33}.scrollbar[data-v-24f2c992]::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 5px rgba(227,227,227,.2);border-radius:0;background:#0000001a}.list-container[data-v-24f2c992]{height:350px;width:100%;background-color:var(--bg-color)}.list-container .scroller-container[data-v-24f2c992]{position:relative;width:100%;height:100%;overflow:auto;--webkit-overflow-scrolling: touch}.list-container .scroller-container .pillar[data-v-24f2c992]{position:absolute;left:0;top:0;right:0;z-index:-1}.list-container .scroller-container .list[data-v-24f2c992]{position:absolute;top:0;left:0;right:0}.list-container .scroller-container .list .item[data-v-24f2c992]{box-sizing:border-box;display:flex;align-items:center;width:100%;height:50px;padding:0 20px;border-bottom:1px solid var(--bg-color-container)}.list-container .scroller-container .list .item[data-v-24f2c992] .ant-list-item{display:flex;justify-content:space-between;align-items:center;width:100%}.list-container .scroller-container .list .item[data-v-24f2c992] .ant-list-item .ant-list-item-action{margin-top:18px}.list-container .scroller-container .list .item[data-v-24f2c992] .ant-list-item>div:nth-child(1){flex:1}.list-container .scroller-container .list .item[data-v-24f2c992] .ant-list-item-meta{display:flex}.list-container .scroller-container .list .item[data-v-24f2c992] .ant-list-item-meta .ant-list-item-meta-avatar{margin-right:15px}.a-extra{display:flex;align-items:center;justify-content:end}

1
web/dist/assets/card-list-DxdnVGe9.js vendored Normal file
View File

@ -0,0 +1 @@
import{_ as g}from"./index-1DQ9lz7_.js";import{_ as f}from"./index-C-JhWVfG.js";import{H as h,a9 as v,ab as w,aa as k}from"./antd-vtmm7CAy.js";import{f as x,a2 as e,a9 as i,aa as s,a4 as t,k as a,G as y,a3 as b,F as z,aj as j,u as V,ad as r}from"./vue-Dl1fzmsf.js";import"./context-Dawj80bg.js";const E={class:"mt-2"},A={class:"flex h-27"},B={class:"w-10 h-10 bg-gray-300 rounded-full"},C=["src"],F={class:"ml"},K={style:{"font-size":"18px","font-weight":"500"}},I="在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。",L={__name:"card-list",setup(N){const c=x([{title:"Aipay",link:"https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png"},{title:"Ant Design Vue",link:"https://www.antdv.com/assets/logo.1ef800a8.svg"},{title:"Vue",link:"https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png"},{title:"Vite",link:"https://cn.vitejs.dev/logo.svg"},{title:"React",link:"https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png"},{title:"Antdv Pro",link:"/logo.svg"},{title:"Webpack",link:"https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png"},{title:"Angular",link:"https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png"}]);return(O,o)=>{const p=h,n=v,_=w,d=k,m=g;return e(),i(m,null,{default:s(()=>[t("div",E,[a(d,{gutter:16},{default:s(()=>[a(n,{xs:16,sm:8,md:6,lg:6,xl:6,class:"mb-4"},{default:s(()=>[a(p,{class:"w-1/1 h-204px",type:"dashed"},{default:s(()=>o[0]||(o[0]=[y(" +新增产品 ")])),_:1})]),_:1}),(e(!0),b(z,null,j(V(c),(l,u)=>(e(),i(n,{key:u,xs:16,sm:8,md:6,lg:6,xl:6,class:"mb-4"},{default:s(()=>[a(_,{bordered:!1,style:{borderRadius:"0"},class:"cursor-pointer hover:shadow-[0_4px_20px_-5px_rgba(0,0,0,0.35)] transition duration-300"},{default:s(()=>[t("div",A,[t("div",B,[t("img",{class:"w-10 h-10 rounded-full",src:l.link},null,8,C)]),t("div",F,[t("div",K,r(l.title),1),t("div",{class:"h-17 overflow-hidden overflow"},r(I))])])]),actions:s(()=>o[1]||(o[1]=[t("li",null,"操作一",-1),t("li",null,"操作二",-1)])),_:2},1024)]),_:2},1024))),128))]),_:1})])]),_:1})}}},G=f(L,[["__scopeId","data-v-9ea2ae90"]]);export{G as default};

View File

@ -0,0 +1 @@
.overflow[data-v-9ea2ae90]{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;overflow:hidden}

1
web/dist/assets/category-Coa7I0Sg.css vendored Normal file
View File

@ -0,0 +1 @@
.category-other-item .ant-form-item{margin-bottom:0}

1
web/dist/assets/category-KW2cQk4I.js vendored Normal file
View File

@ -0,0 +1 @@
import{ad as x,a1 as w,F as C,v as B,bh as F,V as L,ab as V}from"./antd-vtmm7CAy.js";import{s as _,f as N,a2 as r,a9 as p,aa as t,k as a,a4 as d,a3 as $,F as S,aj as T,u as c,G as j,ad as D}from"./vue-Dl1fzmsf.js";const E={class:"flex flex-wrap gap-2"},G={class:"flex gap-4 category-other-item"},I={__name:"category",setup(R){const u=_([{name:"全部",key:"all"},...["一","二","三","四","五","六","七","八","九","十","十一","十二"].map((n,l)=>({name:`类目${n}`,key:`category${l+1}`}))]),e=N([]);function f(n){if(n.key==="all"){if(e.value.includes("all")){e.value=[];return}else e.value=u.value.map(l=>l.key);return}e.value.includes(n.key)?(e.value=e.value.filter(l=>l!==n.key),e.value.includes("all")&&(e.value=e.value.filter(l=>l!=="all"))):(e.value=[...e.value,n.key],e.value.length===u.value.length-1&&(e.value=[...e.value,"all"]))}const m=_([{label:"付晓晓",value:"付晓晓"},{label:"周毛毛",value:"周毛毛"}]),v=_([{label:"优秀",value:1},{label:"普通",value:2}]);return(n,l)=>{const k=x,o=w,h=C,i=B,y=F,g=L,b=V;return r(),p(b,{bordered:!1},{default:t(()=>[a(g,null,{default:t(()=>[a(o,{label:"所属类目"},{default:t(()=>[d("div",E,[(r(!0),$(S,null,T(c(u),s=>(r(),p(k,{key:s.key,"cursor-pointer":"",color:c(e).includes(s.key)?"#108ee9":"",onClick:z=>f(s)},{default:t(()=>[j(D(s.name),1)]),_:2},1032,["color","onClick"]))),128))])]),_:1}),a(h,{dashed:""}),a(o,{label:"其他选项"},{default:t(()=>[a(y,null,{default:t(()=>[d("div",G,[a(o,{label:"作者"},{default:t(()=>[a(i,{placeholder:"不限",style:{width:"100px"},options:c(m)},null,8,["options"])]),_:1}),a(o,{label:"好评度"},{default:t(()=>[a(i,{placeholder:"不限",style:{width:"100px"},options:c(v)},null,8,["options"])]),_:1})])]),_:1})]),_:1})]),_:1})]),_:1})}}};export{I as _};

1
web/dist/assets/center-Bu5yfVri.css vendored Normal file
View File

@ -0,0 +1 @@
[data-v-3eca9ca5] .ant-list-item{flex-direction:column!important;align-items:normal!important}[data-v-3eca9ca5] .ant-btn{padding-left:0}

1
web/dist/assets/center-CXB0VWrh.js vendored Normal file

File diff suppressed because one or more lines are too long

1
web/dist/assets/common-ChpZVeoj.js vendored Normal file
View File

@ -0,0 +1 @@
import{w as S,x as o}from"./index-C-JhWVfG.js";import{u as s,S as g,ae as _,n as k,a2 as c,a3 as v,a4 as N,G as n,ad as I,k as a,aa as t,a9 as u,m as D}from"./vue-Dl1fzmsf.js";import{G as M,H as B,S as E}from"./antd-vtmm7CAy.js";const V={__name:"index",props:{access:{type:[String,Number,Array],required:!0}},setup(d){const{hasAccess:l}=S();return(m,x)=>s(l)(d.access)?g(m.$slots,"default",{key:0}):_("",!0)}},w={class:"flex flex-col gap-2"},R={class:"c-primary"},G={__name:"common",setup(d){const{hasAccess:l,roles:m}=S();return(x,e)=>{var y;const p=M,r=B,f=V,i=E,A=k("access");return c(),v("div",w,[N("div",null,[e[0]||(e[0]=n(" 当前用户拥有权限列表 ")),N("a",R,I((y=s(m))==null?void 0:y.join(",")),1)]),e[7]||(e[7]=n(" 所有用户均可查看 细粒度控制到按钮级别 ")),a(p,{message:"使用Access组件"}),a(i,null,{default:t(()=>[a(f,{access:[s(o).USER,s(o).ADMIN]},{default:t(()=>[a(r,null,{default:t(()=>e[1]||(e[1]=[n("普通用户")])),_:1})]),_:1},8,["access"]),a(f,{access:s(o).ADMIN},{default:t(()=>[a(r,{type:"primary"},{default:t(()=>e[2]||(e[2]=[n(" 管理员 ")])),_:1})]),_:1},8,["access"])]),_:1}),a(p,{message:"使用useAccess组合式Api"}),a(i,null,{default:t(()=>[s(l)([s(o).USER,s(o).ADMIN])?(c(),u(r,{key:0},{default:t(()=>e[3]||(e[3]=[n(" 普通用户 ")])),_:1})):_("",!0),s(l)(s(o).ADMIN)?(c(),u(r,{key:1,type:"primary"},{default:t(()=>e[4]||(e[4]=[n(" 管理员 ")])),_:1})):_("",!0)]),_:1}),a(p,{message:"使用v-access指令"}),a(i,null,{default:t(()=>[D((c(),u(r,null,{default:t(()=>e[5]||(e[5]=[n(" 普通用户 ")])),_:1})),[[A,[s(o).USER,s(o).ADMIN]]]),D((c(),u(r,{type:"primary"},{default:t(()=>e[6]||(e[6]=[n(" 管理员 ")])),_:1})),[[A,s(o).ADMIN]])]),_:1})])}}};export{G as default};

View File

@ -0,0 +1 @@
import{_ as o}from"./index-C-JhWVfG.js";import{a6 as r}from"./antd-vtmm7CAy.js";import{a2 as e,a9 as s}from"./vue-Dl1fzmsf.js";const c={};function a(n,_){const t=r;return e(),s(t,{status:"404",title:"页面配置错误","sub-title":"动态配置页面不存在,请检查配置项"})}const i=o(c,[["render",a]]);export{i as default};

1
web/dist/assets/context-Dawj80bg.js vendored Normal file
View File

@ -0,0 +1 @@
import"./index-C-JhWVfG.js";import{aw as J,s as b,c as t,r as x,w as m}from"./vue-Dl1fzmsf.js";function Z(e){return{type:String,default:e}}function _(e){return{type:Number,default:e}}function $(e){return{type:Boolean,default:e}}function p(e){return{type:Array,default:e}}function ee(){return{type:[Function,Array]}}function K(e,...u){if(typeof e=="function")return e(...u);if(Array.isArray(e))return e.map(f=>f(...u))}function Q(e,u={}){const f=b(!1),H=t(()=>e.logo),W=t(()=>e.title),v=t(()=>e.layout),w=t(()=>e.collapsedWidth),C=t(()=>e.siderWidth),o=t(()=>e.menuData),i=t(()=>e.splitMenus),T=t(()=>e.fixedHeader),A=t(()=>e.fixedSider),D=t(()=>e.collapsed),L=t(()=>e.theme),j=t(()=>e.headerHeight),k=t(()=>e.contentWidth),E=t(()=>e.copyright),S=t(()=>e.isMobile),y=b(!1),F=()=>{y.value=!y.value},P=t(()=>e.header),U=t(()=>e.menu),B=t(()=>e.footer),I=t(()=>e.menuHeader),N=t(()=>e.leftCollapsed),d=x(new Map),l=x({selectedKeys:[]});m(o,()=>{var n;d.clear(),(n=o.value)==null||n.forEach(a=>{d.set(a.path,a)})},{immediate:!0});const O=t(()=>{var a,s;if(S.value||v.value!=="mix"||!i.value)return o.value;const n=(a=l.selectedKeys)==null?void 0:a[0];return n?((s=d.get(n))==null?void 0:s.children)??[]:[]}),R=n=>{l.selectedKeys=n},g=t(()=>e.openKeys),r=t(()=>e.selectedKeys),q=n=>{K(e["onUpdate:openKeys"],n)},z=n=>{K(e["onUpdate:selectedKeys"],n)},G=n=>{K(e.onMenuSelect,n)},h=(n,a,s)=>{for(const c of a??[]){if(c.path===n)return s??c;if(c.children&&c.children.length){const M=h(n,c.children,s??c);if(M)return M}}};return m(r,()=>{var n;if(i.value){const a=(n=r.value)==null?void 0:n[0];if(a){const s=h(a,o.value??[]);s&&(l.selectedKeys=[s.path])}}},{immediate:!0}),m(i,()=>{var n,a;if(!i.value)l.selectedKeys=[];else{const s=((n=r.value)==null?void 0:n[0])??((a=g.value)==null?void 0:a[0])??"",c=h(s,o.value??[]);c?l.selectedKeys=[c==null?void 0:c.path]:l.selectedKeys=[]}}),{logo:H,title:W,layout:v,collapsed:D,leftCollapsed:N,collapsedWidth:w,menuData:o,siderWidth:C,fixedHeader:T,fixedSider:A,headerHeight:j,theme:L,isMobile:S,mobileCollapsed:y,contentWidth:k,copyright:E,hasPageContainer:f,splitMenus:i,splitState:l,menuDataMap:d,selectedMenus:O,handleMobileCollapsed:F,header:P,menu:U,footer:B,openKeys:g,handleOpenKeys:q,selectedKeys:r,handleSelectedKeys:z,handleMenuSelect:G,handleSplitSelectedKeys:R,menuHeader:I,...u}}const[te,V]=J(Q),ne=()=>V();export{p as a,$ as b,te as c,ee as e,_ as n,Z as s,ne as u};

View File

@ -0,0 +1 @@
.system-crud-wrapper .ant-form-item[data-v-00d2ba9b]{margin:0}

Some files were not shown because too many files have changed in this diff Show More