init: taotie-api 项目初始化

This commit is contained in:
2026-05-16 00:14:19 +08:00
commit eb15ef4b87
33 changed files with 1746 additions and 0 deletions

16
.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
# Binaries
tmp/
# IDE
.idea/
.vscode/
*.swp
# OS
.DS_Store
# Env
.env
# Go
*.exe

64
CODEBUDDY.md Normal file
View File

@@ -0,0 +1,64 @@
# CODEBUDDY.md
本文件为 CodeBuddy Code 在本仓库中工作时提供指导。
## 构建与运行命令
| 操作 | 命令 |
|---|---|
| 构建 | `go build -o tmp/main .` |
| 运行 | `go run .` |
| Wire 代码生成 | `go generate ./...`(修改 `wire.go` 后必须运行) |
| 整理依赖 | `go mod tidy` |
| 测试 | `go test ./...`(暂无测试文件) |
应用启动端口为 8000配置在 `config/config.dev.yml`)。环境由 `.env` 控制(`ENV=dev` 对应 `config/config.{env}.yml`)。
## 架构
Go 后端 API使用 Gin + MongoDB + Wire 依赖注入。经典三层架构DTO/DO/PO 模型分离。
### 请求流程
```
Client → Gin Router → wrap.Wrap[R,P](绑定/校验/响应)→ API Handler → Service → Repo → MongoDB
```
### 各层职责
- **API**`api/`路由、中间件、HTTP 处理器。`wrap.Wrap[R,P]()` 泛型包装器统一处理 JSON 绑定、参数校验gookit/validate和统一响应格式`Resp{code, msg, data, ok, time}`)。
- **Service**`service/`业务逻辑。Wire 注入集定义在 `service.go`
- **Repo**`repo/`):数据访问。`BaseRepo[T]` 泛型仓储提供 CRUD 操作,支持软删除和租户隔离。子类添加实体特定查询。
- **Model**`model/`
- `dto/` — API 请求/响应结构体
- `do/` — Service 层输入/输出结构体
- `po/` — 数据库实体,均嵌入 `TBase`OID、时间戳、创建人、tenantId
### 关键设计模式
- **多租户隔离**`BaseRepo.RawProcessFilter` 自动从 context 注入 `tenantId`(通过 `sctx` 包)。所有 PO 实体继承 `TBase.Tenant_OID`
- **Wire 依赖注入**`wire.go`(构建标签 `wireinject`)定义注入图,`wire_gen.go` 为自动生成代码。注入链:`Configuration → MongoDb → Repos → Services → APIs → Router → Engine`
- **泛型 wrap 处理器**`wrap.Wrap[R, P]()` 将请求绑定到 R校验后调用 `func(context.Context, *R) (*P, error)`,成功返回 `Ok(c, data)`,失败返回 `Err(c, err)`
- **错误码**:系统错误定义在 `common/errsys.go`0=成功50-53=内部错误s0xxx=业务通用),用户错误定义在 `common/erruser.go`u001-u004
### API 路由
```
GET / → Hello World
POST /api/v1/login → 用户登录(需 tenantNo + userName + password
POST /api/v1/register → 用户注册
GET /api/v1/user/current → 获取当前用户信息(需认证)
```
### 配置加载
1. `.env` 设置 `ENV` 变量
2. 通过 Viper 加载 `config/config.{ENV}.yml`
3. 详见 `core/config.go` 中的 `Configuration` 结构体和默认值
## 开发约定
- 修改 `wire.go` 后,务必运行 `go generate ./...` 重新生成 `wire_gen.go`
- 新增 API 端点:在 `model/dto/` 定义 DTO`api/v1/` 添加处理器,在 `api/api.go` 注册路由,实现 service 方法,按需添加 repo 方法。
- 新增实体:在 `model/po/` 创建 PO嵌入 `TBase`),在 `model/do/` 创建 DO创建 `XxxRepo` 继承 `BaseRepo[T]`,添加 Wire provider。
- 新增错误码:按现有模式在 `common/errsys.go``common/erruser.go` 中添加业务错误。

45
api/api.go Normal file
View File

@@ -0,0 +1,45 @@
package api
import (
"taotie-api/api/middleware"
v1 "taotie-api/api/v1"
"taotie-api/api/wrap"
"taotie-api/core"
"github.com/gin-gonic/gin"
"github.com/google/wire"
)
// 依赖注入节点
var RouterProd = wire.NewSet(
NewRouter,
v1.NewUserApi,
)
func NewRouter(cfg *core.Configuration, userApi *v1.UserApi) *gin.Engine {
r := gin.Default()
// cors
r.Use(middleware.Cors())
// 定义路由
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Hello, World!",
})
})
apiv1 := r.Group("/api/v1")
{
apiv1.POST("/login", wrap.Wrap(userApi.Login))
apiv1.POST("/register", wrap.Wrap(userApi.Register))
}
apiv1_user := apiv1.Group("/user")
apiv1_user.Use(middleware.Auth(cfg))
{
apiv1_user.GET("/current", wrap.Wrap(userApi.GetCurrentUserInfo))
}
return r
}

View File

@@ -0,0 +1,57 @@
package middleware
import (
"net/http"
"strings"
"taotie-api/common"
"taotie-api/core"
"taotie-api/utils/sctx"
"taotie-api/utils/sjwt"
"github.com/gin-gonic/gin"
)
func Auth(cfg *core.Configuration) gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusOK, gin.H{
"code": common.ErrSysValidationFailed.Info().Id,
"msg": "缺少 Authorization 头",
"ok": false,
})
c.Abort()
return
}
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
c.JSON(http.StatusOK, gin.H{
"code": common.ErrSysValidationFailed.Info().Id,
"msg": "Authorization 格式错误,应为 Bearer <token>",
"ok": false,
})
c.Abort()
return
}
claims, err := sjwt.ParseToken(parts[1], cfg.JWT.SignString)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": common.ErrSysValidationFailed.Info().Id,
"msg": "token 无效或已过期",
"ok": false,
})
c.Abort()
return
}
sctx.SetCurrentUser(c, &sctx.CurrentUser{
UserId: claims.UserId,
UserName: claims.UserName,
TenantId: claims.TenantId,
})
c.Next()
}
}

View File

@@ -0,0 +1,19 @@
package middleware
import (
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func Cors() gin.HandlerFunc {
return cors.New(cors.Config{
AllowOrigins: []string{"*"}, // 允许所有来源(生产环境不建议使用)
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, // 允许的请求方法
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, // 允许的请求头部
ExposeHeaders: []string{"Content-Length", "Access-Control-Allow-Origin", "Authorization"}, // 允许客户端获取的响应头部
AllowCredentials: true, // 允许携带 Cookie
MaxAge: 12 * time.Hour, // 预检请求的缓存时间
})
}

85
api/v1/userapi.go Normal file
View File

@@ -0,0 +1,85 @@
package v1
import (
"context"
"taotie-api/common"
"taotie-api/model/do/userdo"
"taotie-api/model/dto/userdto"
"taotie-api/service"
"taotie-api/utils/sctx"
)
type UserApi struct {
userService *service.UserService
}
func NewUserApi(userService *service.UserService) *UserApi {
return &UserApi{userService}
}
// Login 登录
func (api *UserApi) Login(ctx context.Context, req *userdto.LoginReq) (*userdto.LoginRes, error) {
out, err := api.userService.Login(ctx, &userdo.LoginIn{
TenantNo: req.TenantNo,
UserName: req.UserName,
Password: req.Password,
})
if err != nil {
return nil, err
}
// 登录成功,返回 token
tokenOut, err := api.userService.GenToken(ctx, &userdo.GenTokenIn{
TenantNo: out.TenantId,
UserId: out.UserId,
UserName: out.UserName,
})
if err != nil {
return nil, err
}
return &userdto.LoginRes{
Token: tokenOut.Token,
}, nil
}
// Register 注册
func (api *UserApi) Register(ctx context.Context, req *userdto.RegisterReq) (*userdto.RegisterRes, error) {
// 创建租户和用户
out, err := api.userService.Create(ctx, &userdo.CreateIn{
TenantName: req.TenantName,
UserName: req.UserName,
Password: req.Password,
})
if err != nil {
return nil, err
}
// 自动登录,生成 token
tokenOut, err := api.userService.GenToken(ctx, &userdo.GenTokenIn{
TenantNo: out.TenantId,
UserId: out.UserId,
UserName: out.UserName,
})
if err != nil {
return nil, err
}
return &userdto.RegisterRes{
Token: tokenOut.Token,
}, nil
}
// GetCurrentUserInfo 获取当前用户信息
func (api *UserApi) GetCurrentUserInfo(ctx context.Context, req *userdto.GetCurrentUserInfoReq) (*userdto.GetCurrentUserInfoRes, error) {
cuser := sctx.GetCurrentUser(ctx)
if cuser == nil {
return nil, common.ErrUserNotFound
}
return &userdto.GetCurrentUserInfoRes{
UserId: cuser.UserId,
UserName: cuser.UserName,
TenantId: cuser.TenantId,
}, nil
}

110
api/wrap/wrap.go Normal file
View File

@@ -0,0 +1,110 @@
package wrap
import (
"context"
"fmt"
"log/slog"
"reflect"
"taotie-api/common"
"time"
"github.com/duke-git/lancet/v2/xerror"
"github.com/gin-gonic/gin"
"github.com/gookit/validate"
)
func Wrap[R, P any](fn func(context.Context, *R) (*P, error)) func(c *gin.Context) {
return func(c *gin.Context) {
// defer func() {
// if r := recover(); r != nil {
// Err(c, errors.New(fmt.Sprintf("panic: %v", r)))
// c.Abort()
// }
// }()
// 绑定请求参数
var req = new(R)
// 检查请求结构体是否为空结构体
reqType := reflect.TypeOf(req).Elem()
isEmptyStruct := reqType.NumField() == 0
if !isEmptyStruct {
if c.Request.ContentLength != 0 {
// 绑定请求参数
if err := c.ShouldBindJSON(req); err != nil {
er := common.ErrSysValidationFailed.Wrap(err)
Err(c, er)
c.Abort()
return
}
}
// 校验请求参数
v := validate.New(req)
if !v.Validate() {
er := common.ErrSysValidationFailed.Wrap(v.Errors.OneError())
Err(c, er)
c.Abort()
return
}
}
// 调用业务逻辑
res, err := fn(c, req)
if err != nil {
Err(c, err)
c.Abort()
return
}
Ok(c, res)
}
}
type Resp struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data any `json:"data"`
Ok bool `json:"ok"`
Time int64 `json:"time"`
}
func Ok(c *gin.Context, data any) {
c.JSON(200, Resp{
Code: common.ErrSysOk.Info().Id,
Msg: common.ErrSysOk.Info().Message,
Data: data,
Ok: true,
Time: time.Now().Unix(),
})
}
// Err 处理错误
// // 注意⚠️:这里需要想想是不是所有的错误都要返回给 web 端
func Err(c *gin.Context, err error) {
// 返回给 web 端的错误
var weberr = common.ErrSysInternal
// 提取最底层的错误
causeErr := xerror.Unwrap(err)
// 如果是自定义错误,解析错误,返回给 web 端
if causeErr != nil {
weberr = causeErr
} else {
slog.Error("【SRV-ERR】", "ERR", fmt.Errorf("%+v", err))
}
// 系统内部错误的话,就不返回给 web 端,只记录。
// 记录日志
// slog.Error("【WEB-ERR】", "ERR", weberr)
// slog.Error("【SRV-ERR】", "ERR", fmt.Errorf("%+v", err))
c.JSON(200, Resp{
Code: weberr.Info().Id,
Msg: weberr.Info().Message,
Ok: false,
Time: time.Now().Unix(),
})
}

38
common/common.go Normal file
View File

@@ -0,0 +1,38 @@
package common
type (
Map = map[string]any
MapAnyAny = map[any]any
MapAnyStr = map[any]string
MapAnyInt = map[any]int
MapAnyBool = map[any]bool
MapStrAny = map[string]any
MapStrStr = map[string]string
MapStrInt = map[string]int
MapStrBool = map[string]bool
MapIntAny = map[int]any
MapIntStr = map[int]string
MapIntInt = map[int]int
MapIntBool = map[int]bool
)
type (
List = []Map
ListAnyAny = []MapAnyAny
ListAnyStr = []MapAnyStr
ListAnyInt = []MapAnyInt
ListAnyBool = []MapAnyBool
ListStrAny = []MapStrAny
ListStrStr = []MapStrStr
ListStrInt = []MapStrInt
ListStrBool = []MapStrBool
ListIntAny = []MapIntAny
ListIntStr = []MapIntStr
ListIntInt = []MapIntInt
ListIntBool = []MapIntBool
)
const (
False = 0
True = 1
)

18
common/errsys.go Normal file
View File

@@ -0,0 +1,18 @@
package common
import (
"github.com/duke-git/lancet/v2/xerror"
)
var (
ErrSysOk = xerror.New("ok").Id("0") // 成功
ErrSysInternal = xerror.New("未知内部错误").Id("50") // 未知内部错误
ErrSysNil = xerror.New("空指针").Id("51") // 空指针
ErrSysValidationFailed = xerror.New("校验失败").Id("52") // 校验失败
ErrSysDbError = xerror.New("数据库错误").Id("53") // 数据库错误
ErrSysDuplicate = xerror.New("重复数据").Id("s0001") // 重复数据
ErrSysInvalid = xerror.New("数据无效").Id("s0002") // 数据无效
ErrSysDataNotFound = xerror.New("数据不存在").Id("s0003") // 数据不存在
)

10
common/erruser.go Normal file
View File

@@ -0,0 +1,10 @@
package common
import "github.com/duke-git/lancet/v2/xerror"
var (
ErrUserNotFound = xerror.New("用户不存在").Id("u001") // 用户不存在
ErrUserPasswordError = xerror.New("密码错误").Id("u002") // 密码错误
ErrUserTenantNotFound = xerror.New("租户不存在").Id("u003") // 租户不存在
ErrUserGenTokenFailed = xerror.New("生成token失败").Id("u004") // 生成token失败
)

22
config/config.dev.yml Normal file
View File

@@ -0,0 +1,22 @@
server:
port: 8000
log:
# const (
# LevelDebug Level = -4
# LevelInfo Level = 0
# LevelWarn Level = 4
# LevelError Level = 8
# )
path: log/app.log
level: -4
db:
mongoUri: mongodb://localhost:27017
dbName: taotie
jwt:
# 签名字符串
signString: taotie-api-q2e
# 过期时间,单位秒
timeOut: 3600

84
core/config.go Normal file
View File

@@ -0,0 +1,84 @@
package core
import (
"fmt"
"log/slog"
"os"
"github.com/joho/godotenv"
"github.com/spf13/viper"
)
type Configuration struct {
Server struct {
Port string `yaml:"port"`
} `yaml:"server"`
Log struct {
Path string `json:"path"`
Level slog.Level `json:"level"`
} `yaml:"log"`
DB struct {
MongoURI string `yaml:"mongoUri"`
DBName string `yaml:"dbName"`
} `yaml:"db"`
// JWT 配置
JWT struct {
SignString string `yaml:"signString"`
TimeOut int `yaml:"timeOut"`
} `yaml:"jwt"`
Viper *viper.Viper `yaml:"-"`
}
// 初始化配置
func NewConfiguration() (cfg *Configuration, err error) {
cfg = &Configuration{}
// 加载 .env 文件
err = godotenv.Load()
if err != nil {
return nil, err
}
// 读取环境配置
env := os.Getenv("ENV")
filename := ""
if env == "" {
filename = "config/config.yml"
} else {
filename = fmt.Sprintf("config/config.%v.yml", env)
}
// 读取配置文件
v := viper.New()
// 设置默认值
v.SetDefault("server.port", "8000")
v.SetDefault("log.path", "log/app.log")
v.SetDefault("log.level", 0)
v.SetDefault("jwt.signString", "taotie-api-q2e")
v.SetDefault("jwt.timeOut", 3600)
v.SetDefault("db.mongoUri", "mongodb://localhost:27017")
v.SetDefault("db.dbName", "taotie")
// 设置配置文件路径
v.SetConfigFile(filename)
// 试着读取文件
err = v.ReadInConfig()
if err != nil {
return nil, err
}
err = v.Unmarshal(cfg)
if err != nil {
return nil, err
}
cfg.Viper = v
return cfg, nil
}

68
go.mod Normal file
View File

@@ -0,0 +1,68 @@
module taotie-api
go 1.26.1
require (
github.com/duke-git/lancet v1.4.6
github.com/duke-git/lancet/v2 v2.3.9
github.com/gin-contrib/cors v1.7.7
github.com/gin-gonic/gin v1.12.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/wire v0.7.0
github.com/gookit/goutil v0.7.4
github.com/gookit/validate v1.5.7
github.com/joho/godotenv v1.5.1
github.com/spf13/viper v1.21.0
go.mongodb.org/mongo-driver v1.17.9
)
require (
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/gin-contrib/sse v1.1.0 // 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.30.1 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gookit/filter v1.2.3 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.6 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.2.0 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.23.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.35.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
)

184
go.sum Normal file
View File

@@ -0,0 +1,184 @@
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
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/duke-git/lancet v1.4.6 h1:pFTA06baQ8OceOmJB9tOsGz60y6GsfXOevIJVIFhGfg=
github.com/duke-git/lancet v1.4.6/go.mod h1:Grr6ehF0ig2nRIjeb+NmcxiJ12mkML4XQAx95tlQeJU=
github.com/duke-git/lancet/v2 v2.3.9 h1:ZxUvfoEY7YbsGIeoXRxHWIkRCAt6VN7UBKWgCCqBB3U=
github.com/duke-git/lancet/v2 v2.3.9/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/cors v1.7.7 h1:Oh9joP463x7Mw72vhvJ61YQm8ODh9b04YR7vsOErD0Q=
github.com/gin-contrib/cors v1.7.7/go.mod h1:K5tW0RkzJtWSiOdikXloy8VEZlgdVNpHNw8FpjUPNrE=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
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.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
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/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
github.com/gookit/filter v1.2.3 h1:Zo7cBOtsVzAoa/jtf+Ury6zlsbJXqInFdUpbbnB2vMM=
github.com/gookit/filter v1.2.3/go.mod h1:nFLJcOV8dRgS1iiX23gUQgmHUhpuS40qCvAGgIvA1pM=
github.com/gookit/goutil v0.7.4 h1:OWgUngToNz+bPlX5aP+EMG31DraEU63uvKMwwT3vseM=
github.com/gookit/goutil v0.7.4/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
github.com/gookit/validate v1.5.7 h1:W0nqxAbgCJ4ND/oR+Tx8yyYn6l+PkFVvGHlG7LxpYsc=
github.com/gookit/validate v1.5.7/go.mod h1:VeThM1saXWVJIG/Yn627PJL9yvap0s4bsjPUIV8I0UI=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/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/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/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
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/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
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.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=

29
main.go Normal file
View File

@@ -0,0 +1,29 @@
package main
import (
"log/slog"
"taotie-api/core"
"github.com/gookit/validate/locales/zhcn"
)
func main() {
// 初始化应用
r, err := InitApp()
if err != nil {
panic(err)
}
// 初始化配置
cfg, err := core.NewConfiguration()
if err != nil {
slog.Error("初始化配置失败", "error", err)
}
// for all Validation.
// NOTICE: 必须在调用 validate.New() 前注册, 它只需要一次调用。
zhcn.RegisterGlobal()
// 启动服务器
r.Run(":" + cfg.Server.Port)
}

View File

@@ -0,0 +1,9 @@
package tenantdo
type CreateIn struct {
TenantName string `json:"tenantName"`
}
type CreateOut struct {
Id string `json:"id"`
}

35
model/do/userdo/enter.go Normal file
View File

@@ -0,0 +1,35 @@
package userdo
type LoginIn struct {
TenantNo string `json:"tenantNo"`
UserName string `json:"userName"`
Password string `json:"password"`
}
type LoginOut struct {
TenantId string `json:"tenantId"`
UserId string `json:"userId"`
UserName string `json:"userName"`
}
type GenTokenIn struct {
TenantNo string `json:"tenantNo"`
UserId string `json:"userId"`
UserName string `json:"userName"`
}
type GenTokenOut struct {
Token string `json:"token"`
}
type CreateIn struct {
TenantName string `json:"tenantName"`
UserName string `json:"userName"`
Password string `json:"password"`
}
type CreateOut struct {
TenantId string `json:"tenantId"`
UserId string `json:"userId"`
UserName string `json:"userName"`
}

View File

@@ -0,0 +1,29 @@
package userdto
type LoginReq struct {
TenantNo string `json:"tenantNo"`
UserName string `json:"userName"`
Password string `json:"password"`
}
type LoginRes struct {
Token string `json:"token"`
}
type RegisterReq struct {
TenantName string `json:"tenantName"`
UserName string `json:"userName"`
Password string `json:"password"`
}
type RegisterRes struct {
Token string `json:"token"`
}
type GetCurrentUserInfoReq struct{}
type GetCurrentUserInfoRes struct {
UserId string `json:"userId"`
UserName string `json:"userName"`
TenantId string `json:"tenantId"`
}

43
model/po/tbase.go Normal file
View File

@@ -0,0 +1,43 @@
package po
import "go.mongodb.org/mongo-driver/bson/primitive"
type TBase struct {
OID primitive.ObjectID `bson:"_id,omitempty"` // 主键
CreatedAt int64 `bson:"createdAt"` // 创建时间
UpdatedAt int64 `bson:"updatedAt"` // 更新时间
DeletedAt int64 `bson:"deletedAt"` // 删除时间
CreatedBy_OID primitive.ObjectID `bson:"createdBy"` // 创建人OID
Tenant_OID primitive.ObjectID `bson:"tenantId"` // 租户ID
}
// Id 获取主键
func (t *TBase) Id() string {
return t.OID.Hex()
}
// CreatedBy 获取创建人 Id
func (t *TBase) CreatedBy() string {
return t.CreatedBy_OID.Hex()
}
// SetCreatedBy 设置创建人 Id
func (t *TBase) SetCreatedBy(createdBy string) {
t.CreatedBy_OID, _ = primitive.ObjectIDFromHex(createdBy)
}
// TenantId 获取租户 Id
func (t *TBase) TenantId() string {
return t.Tenant_OID.Hex()
}
// SetTenantId 设置租户 Id
func (t *TBase) SetTenantId(tenantId string) {
t.Tenant_OID, _ = primitive.ObjectIDFromHex(tenantId)
}
// SetNow 设置当前时间
func (t *TBase) SetNow(now int64) {
t.CreatedAt = now
t.UpdatedAt = now
}

8
model/po/ttenant.go Normal file
View File

@@ -0,0 +1,8 @@
package po
type TTenant struct {
TBase `bson:",inline"`
TenantNo string `bson:"tenantNo"`
TenantName string `bson:"tenantName"`
}

9
model/po/tuser.go Normal file
View File

@@ -0,0 +1,9 @@
package po
type TUser struct {
TBase `bson:",inline"`
NickName string `bson:"nickName"` // 昵称
UserName string `bson:"userName"` // 用户名
Password string `bson:"password"` // 密码
}

40
repo/mongodb.go Normal file
View File

@@ -0,0 +1,40 @@
package repo
import (
"context"
"taotie-api/core"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type MongoDb struct {
client *mongo.Client
cfg *core.Configuration
}
// NewMongoDb 创建一个新的 MongoDb 实例
func NewMongoDb(cfg *core.Configuration) (*MongoDb, error) {
ctx := context.Background()
// 连接数据库
client, err := mongo.Connect(ctx, options.Client().ApplyURI(cfg.DB.MongoURI))
if err != nil {
return nil, err
}
// 检查连接是否成功
err = client.Ping(ctx, nil)
if err != nil {
return nil, err
}
return &MongoDb{
client: client,
cfg: cfg,
}, nil
}
// Db 返回数据库实例
func (m *MongoDb) Db() *mongo.Database {
return m.client.Database(m.cfg.DB.DBName)
}

294
repo/repo.go Normal file
View File

@@ -0,0 +1,294 @@
package repo
import (
"context"
"errors"
"reflect"
"taotie-api/utils/sctx"
"time"
"github.com/duke-git/lancet/slice"
"github.com/google/wire"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// 依赖注入节点
var RepoProd = wire.NewSet(
NewUserRepo,
NewTenantRepo,
)
type BaseRepo[T IEntity] struct {
mdb *MongoDb
tableName string
}
// ProcessFilter 处理查询条件
func (rp *BaseRepo[T]) RawProcessFilter(ctx context.Context, qp *QueryParams) bson.M {
filter := bson.M{}
// 查询条件
if len(qp.Where) != 0 {
wh := make(map[string]any, 0)
for k, v := range qp.Where {
if (k[len(k)-2:]) == "Id" {
oid, _ := primitive.ObjectIDFromHex(v.(string))
wh[k] = oid
continue
}
wh[k] = v
}
filter = wh
}
// 查询 ids
if len(qp.WhereInIds) != 0 {
oids := make([]primitive.ObjectID, 0)
for _, id := range qp.WhereInIds {
oid, _ := primitive.ObjectIDFromHex(id)
oids = append(oids, oid)
}
filter["_id"] = bson.M{"$in": oids}
}
// 过滤删除
filter["deletedAt"] = 0
// 过滤租户
cuser := sctx.GetCurrentUser(ctx)
if cuser != nil {
filter["tenantId"], _ = primitive.ObjectIDFromHex(cuser.TenantId)
}
return filter
}
// ProcessSetData 处理需要修改的数据,限制只能修改已拥有的字段
func (rp *BaseRepo[T]) RawProcessSetData(ctx context.Context, setData map[string]any) (map[string]any, error) {
// 提取结构体
t := reflect.TypeFor[T]().Elem()
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
// 提取结构体字段
fields := make([]string, 0, t.NumField())
for i := 0; i < t.NumField(); i++ {
var bs = t.Field(i).Tag.Get("bson")
if bs == ",inline" {
continue
}
fields = append(fields, bs)
}
// 筛选
sd := make(map[string]any, 0)
for k, v := range setData {
if slice.Contain(fields, k) {
// 处理 ObjectID 类型
if (k[len(k)-2:]) == "Id" {
oid, _ := primitive.ObjectIDFromHex(v.(string))
sd[k] = oid
continue
}
if (k[len(k)-3:]) == "Ids" {
oids := make([]primitive.ObjectID, 0)
for _, id := range v.([]any) {
oid, _ := primitive.ObjectIDFromHex(id.(string))
oids = append(oids, oid)
}
sd[k] = oids
continue
}
sd[k] = v
}
}
return sd, nil
}
// Create 创建
func (rp *BaseRepo[T]) Create(ctx context.Context, tdata T) (string, error) {
collection := rp.mdb.Db().Collection(rp.tableName)
// 设置默认值
cuser := sctx.GetCurrentUser(ctx)
tdata.SetCreatedBy(cuser.UserId) // 默认创建人
tdata.SetTenantId(cuser.TenantId) // 默认租户
// 设置基础时间
now := time.Now().UnixMilli()
tdata.SetNow(now)
result, err := collection.InsertOne(ctx, tdata)
if err != nil {
return "", err
}
// 插入成功后返回插入的ID
return result.InsertedID.(primitive.ObjectID).Hex(), nil
}
// CreateMany 创建多个
func (rp *BaseRepo[T]) CreateMany(ctx context.Context, tdatas []T) (ids []string, err error) {
collection := rp.mdb.Db().Collection(rp.tableName)
// 设置默认值
cuser := sctx.GetCurrentUser(ctx)
now := time.Now().UnixMilli()
tdataanys := make([]any, 0, len(tdatas))
for _, v := range tdatas {
if cuser != nil {
v.SetCreatedBy(cuser.UserId) // 默认创建人
v.SetTenantId(cuser.TenantId) // 默认租户
}
// 设置基础时间
v.SetNow(now)
// 转换
tdataanys = append(tdataanys, v)
}
// 插入数据
result, err := collection.InsertMany(ctx, tdataanys)
if err != nil {
return nil, err
}
ids = make([]string, 0, len(result.InsertedIDs))
for _, id := range result.InsertedIDs {
ids = append(ids, id.(primitive.ObjectID).Hex())
}
return ids, nil
}
// Delete 删除(软删除)
func (rp *BaseRepo[T]) Delete(ctx context.Context, qp *QueryParams) error {
collection := rp.mdb.Db().Collection(rp.tableName)
filter := rp.RawProcessFilter(ctx, qp)
// 查询参数不能为空
if len(filter) == 0 {
return errors.New("查询参数不能为空")
}
// 软删除
_, err := collection.UpdateMany(ctx, filter, bson.M{"$set": bson.M{"deletedAt": time.Now().UnixMilli()}})
return err
}
func (rp *BaseRepo[T]) Update(ctx context.Context, qp *QueryParams, setData map[string]any) error {
collection := rp.mdb.Db().Collection(rp.tableName)
sd, err := rp.RawProcessSetData(ctx, setData)
if err != nil {
return err
}
// 检查是否有可更新的字段
if len(sd) == 0 {
return errors.New("更新参数不能为空")
}
// 更新时间
sd["updatedAt"] = time.Now().UnixMilli()
filter := rp.RawProcessFilter(ctx, qp)
if len(filter) == 0 {
return errors.New("查询参数不能为空")
}
// 更新数据
_, err = collection.UpdateMany(ctx, filter, bson.M{"$set": sd})
if err != nil {
return err
}
return nil
}
// Count 查询数量
func (rp *BaseRepo[T]) RawCount(ctx context.Context, qp *QueryParams) (int64, error) {
collection := rp.mdb.Db().Collection(rp.tableName)
filter := rp.RawProcessFilter(ctx, qp)
// 查询总量
total, err := collection.CountDocuments(ctx, filter)
if err != nil {
return 0, err
}
return total, nil
}
// Find 查询
func (rp *BaseRepo[T]) RawFind(ctx context.Context, qp *QueryParams) (results []T, total int64, err error) {
collection := rp.mdb.Db().Collection(rp.tableName)
filter := rp.RawProcessFilter(ctx, qp)
opts := &options.FindOptions{}
// 设置分页
if qp.Page > 0 && qp.PageSize > 0 {
opts.SetSkip(int64((qp.Page - 1) * qp.PageSize))
opts.SetLimit(int64(qp.PageSize))
}
// 设置排序
if len(qp.OrderBy) != 0 {
opts.SetSort(qp.OrderBy)
}
// 执行查询
cursor, err := collection.Find(ctx, filter, opts)
if err != nil {
return nil, 0, err
}
defer cursor.Close(ctx)
// 解析结果
if err = cursor.All(ctx, &results); err != nil {
// 处理空文档错误
if err == mongo.ErrNilDocument {
return nil, 0, nil
}
return nil, 0, err
}
// 查询总量
total, err = collection.CountDocuments(ctx, filter)
if err != nil {
return nil, 0, err
}
return results, total, nil
}
// FindOne 查询单条记录
func (rp *BaseRepo[T]) RawFindOne(ctx context.Context, qp *QueryParams) (result T, err error) {
collection := rp.mdb.Db().Collection(rp.tableName)
filter := rp.RawProcessFilter(ctx, qp)
// 执行查询
err = collection.FindOne(ctx, filter).Decode(&result)
if err != nil {
// 处理空文档错误
if err == mongo.ErrNilDocument {
return result, nil
}
return result, err
}
return result, nil
}

36
repo/tenantrepo.go Normal file
View File

@@ -0,0 +1,36 @@
package repo
import (
"context"
"taotie-api/common"
"taotie-api/model/po"
)
type TenantRepo[T IEntity] struct {
BaseRepo[T]
}
func NewTenantRepo(mdb *MongoDb) *TenantRepo[*po.TTenant] {
return &TenantRepo[*po.TTenant]{
BaseRepo: BaseRepo[*po.TTenant]{
mdb: mdb,
tableName: "ttenant",
},
}
}
func (rp *TenantRepo[T]) FindOneByTenantNo(ctx context.Context, tenantNo string) (result T, err error) {
return rp.RawFindOne(ctx, &QueryParams{
Where: common.Map{
"tenantNo": tenantNo,
},
})
}
func (rp *TenantRepo[T]) FindOneByTenantName(ctx context.Context, tenantName string) (result T, err error) {
return rp.RawFindOne(ctx, &QueryParams{
Where: common.Map{
"tenantName": tenantName,
},
})
}

18
repo/types.go Normal file
View File

@@ -0,0 +1,18 @@
package repo
type IEntity interface {
Id() string
CreatedBy() string
SetCreatedBy(string)
TenantId() string
SetTenantId(string)
SetNow(int64)
}
type QueryParams struct {
Page int
PageSize int
Where map[string]any
WhereInIds []string
OrderBy []string
}

39
repo/userrepo.go Normal file
View File

@@ -0,0 +1,39 @@
package repo
import (
"context"
"taotie-api/common"
"taotie-api/model/po"
)
type UserRepo[T IEntity] struct {
BaseRepo[T]
}
func NewUserRepo(mdb *MongoDb) *UserRepo[*po.TUser] {
return &UserRepo[*po.TUser]{
BaseRepo[*po.TUser]{
mdb: mdb,
tableName: "tuser",
},
}
}
func (rp *UserRepo[T]) FindOneByTenantIdAndUserName(ctx context.Context, tenantId string, userName string) (result T, err error) {
result, err = rp.RawFindOne(ctx, &QueryParams{
Where: common.Map{
"tenantId": tenantId,
"userName": userName,
},
})
if err != nil {
return result, err
}
return result, nil
}
func (rp *UserRepo[T]) Update(ctx context.Context, qp *QueryParams, setData map[string]any) error {
return nil
}

11
service/service.go Normal file
View File

@@ -0,0 +1,11 @@
package service
import (
"github.com/google/wire"
)
// 依赖注入节点
var ServiceProd = wire.NewSet(
NewUserService,
NewTenantService,
)

45
service/tenantservice.go Normal file
View File

@@ -0,0 +1,45 @@
package service
import (
"context"
"taotie-api/common"
"taotie-api/core"
"taotie-api/model/do/tenantdo"
"taotie-api/model/po"
"taotie-api/repo"
"github.com/gookit/goutil/errorx"
)
type TenantService struct {
cfg *core.Configuration
tenantRepo *repo.TenantRepo[*po.TTenant]
}
func NewTenantService(cfg *core.Configuration, tenantRepo *repo.TenantRepo[*po.TTenant]) *TenantService {
return &TenantService{cfg, tenantRepo}
}
func (rp *TenantService) Create(ctx context.Context, in *tenantdo.CreateIn) (*tenantdo.CreateOut, error) {
// 查重
tenant, err := rp.tenantRepo.FindOneByTenantName(ctx, in.TenantName)
if err != nil {
return nil, err
}
if tenant != nil {
return nil, errorx.With(common.ErrSysDuplicate, "租户名称重复")
}
// 创建租户
tenant = &po.TTenant{
TenantName: in.TenantName,
}
tenantId, err := rp.tenantRepo.Create(ctx, tenant)
if err != nil {
return nil, err
}
return &tenantdo.CreateOut{
Id: tenantId,
}, nil
}

125
service/userservice.go Normal file
View File

@@ -0,0 +1,125 @@
package service
import (
"context"
"fmt"
"taotie-api/common"
"taotie-api/core"
"taotie-api/model/do/userdo"
"taotie-api/model/po"
"taotie-api/repo"
"taotie-api/utils/sctx"
"taotie-api/utils/sjwt"
"time"
"github.com/gookit/goutil/errorx"
)
type UserService struct {
cfg *core.Configuration
userRepo *repo.UserRepo[*po.TUser]
tenantRepo *repo.TenantRepo[*po.TTenant]
}
func NewUserService(mdb *repo.MongoDb, cfg *core.Configuration, userRepo *repo.UserRepo[*po.TUser], tenantRepo *repo.TenantRepo[*po.TTenant]) *UserService {
return &UserService{
cfg,
userRepo,
tenantRepo,
}
}
// Login 登录
func (s *UserService) Login(ctx context.Context, in *userdo.LoginIn) (*userdo.LoginOut, error) {
// 验证租户
ttenant, err := s.tenantRepo.FindOneByTenantNo(ctx, in.TenantNo)
if err != nil {
return nil, err
}
if ttenant == nil {
return nil, common.ErrUserTenantNotFound
}
// 验证用户
tuser, err := s.userRepo.FindOneByTenantIdAndUserName(ctx, ttenant.Id(), in.UserName)
if err != nil {
return nil, err
}
if tuser == nil {
return nil, common.ErrUserNotFound
}
if tuser.Password != in.Password {
return nil, common.ErrUserPasswordError
}
return &userdo.LoginOut{
TenantId: ttenant.Id(),
UserId: tuser.Id(),
UserName: tuser.UserName,
}, nil
}
// GenToken 生成token
func (s *UserService) GenToken(ctx context.Context, in *userdo.GenTokenIn) (*userdo.GenTokenOut, error) {
token, err := sjwt.GenerateToken(in.TenantNo, in.UserId, in.UserName, s.cfg.JWT.TimeOut, s.cfg.JWT.SignString)
if err != nil {
return nil, err
}
return &userdo.GenTokenOut{
Token: token,
}, nil
}
func (s *UserService) Create(ctx context.Context, in *userdo.CreateIn) (*userdo.CreateOut, error) {
// 查重:租户名称
ttenant, err := s.tenantRepo.FindOneByTenantName(ctx, in.TenantName)
if err != nil {
return nil, err
}
if ttenant != nil {
return nil, errorx.With(common.ErrSysDuplicate, "租户名称重复")
}
// 先设置临时用户到 context避免 BaseRepo.Create 中 cuser 为 nil 导致 panic
sctx.SetCurrentUser(ctx, &sctx.CurrentUser{
UserId: "",
UserName: in.UserName,
TenantId: "",
})
// 生成 tenantNo基于时间戳
tenantNo := fmt.Sprintf("T%d", time.Now().UnixMilli())
// 创建租户
ttenant = &po.TTenant{
TenantNo: tenantNo,
TenantName: in.TenantName,
}
tenantId, err := s.tenantRepo.Create(ctx, ttenant)
if err != nil {
return nil, err
}
// 更新 context 中的 TenantId
sctx.SetCurrentUser(ctx, &sctx.CurrentUser{
UserId: "",
UserName: in.UserName,
TenantId: tenantId,
})
// 创建用户
tuser := &po.TUser{
UserName: in.UserName,
Password: in.Password,
}
userId, err := s.userRepo.Create(ctx, tuser)
if err != nil {
return nil, err
}
return &userdo.CreateOut{
TenantId: tenantId,
UserId: userId,
UserName: in.UserName,
}, nil
}

42
utils/sctx/sctx.go Normal file
View File

@@ -0,0 +1,42 @@
package sctx
import (
"context"
"github.com/gin-gonic/gin"
)
type MyKey string
const CurrentKey MyKey = "taotie"
type CurrentUser struct {
UserId string
UserName string
TenantId string
}
func SetCurrentUser(ctx context.Context, cuser *CurrentUser) context.Context {
if c, ok := ctx.(*gin.Context); ok {
c.Set(CurrentKey, cuser)
return c
}
return context.WithValue(ctx, CurrentKey, cuser)
}
func GetCurrentUser(ctx context.Context) *CurrentUser {
if c, ok := ctx.(*gin.Context); ok {
if user, ok := c.Get(CurrentKey); ok {
if cuser, ok := user.(*CurrentUser); ok {
return cuser
}
}
}
if cuser, ok := ctx.Value(CurrentKey).(*CurrentUser); ok {
return cuser
}
return nil
}

50
utils/sjwt/sjwt.go Normal file
View File

@@ -0,0 +1,50 @@
package sjwt
import (
"time"
"github.com/golang-jwt/jwt/v5"
)
type MyClaims struct {
jwt.RegisteredClaims
TenantId string `json:"tenantId"`
UserId string `json:"userId"`
UserName string `json:"userName"`
}
// 生成 token
func GenerateToken(tenantId string, userId string, userName string, timeOut int, signedString string) (string, error) {
// 创建一个claims
claims := jwt.NewWithClaims(jwt.SigningMethodHS256, MyClaims{
RegisteredClaims: jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * time.Duration(timeOut))),
Issuer: "taotie",
},
TenantId: tenantId,
UserId: userId,
UserName: userName,
})
// 生成 token
return claims.SignedString([]byte(signedString))
}
// 解析 token
func ParseToken(tokenString string, signedString string) (*MyClaims, error) {
// 解析token
token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (any, error) {
return []byte(signedString), nil
})
if err != nil {
return nil, err
}
// 对token进行断言
if claims, ok := token.Claims.(*MyClaims); ok {
return claims, nil
} else {
return nil, err
}
}

29
wire.go Normal file
View File

@@ -0,0 +1,29 @@
//go:build wireinject
// +build wireinject
package main
import (
"taotie-api/api"
"taotie-api/core"
"taotie-api/repo"
"taotie-api/service"
"github.com/gin-gonic/gin"
"github.com/google/wire"
)
func InitApp() (*gin.Engine, error) {
wire.Build(
core.NewConfiguration,
repo.NewMongoDb,
api.RouterProd,
service.ServiceProd,
repo.RepoProd,
)
return nil, nil
}

35
wire_gen.go Normal file
View File

@@ -0,0 +1,35 @@
// 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 main
import (
"github.com/gin-gonic/gin"
"taotie-api/api"
"taotie-api/api/v1"
"taotie-api/core"
"taotie-api/repo"
"taotie-api/service"
)
// Injectors from wire.go:
func InitApp() (*gin.Engine, error) {
configuration, err := core.NewConfiguration()
if err != nil {
return nil, err
}
mongoDb, err := repo.NewMongoDb(configuration)
if err != nil {
return nil, err
}
userRepo := repo.NewUserRepo(mongoDb)
tenantRepo := repo.NewTenantRepo(mongoDb)
userService := service.NewUserService(mongoDb, configuration, userRepo, tenantRepo)
userApi := v1.NewUserApi(userService)
engine := api.NewRouter(configuration, userApi)
return engine, nil
}