From 5bb025c1aa893c48c97eac95c1a8e5bab19af4c8 Mon Sep 17 00:00:00 2001 From: zhilv Date: Sat, 15 Nov 2025 18:20:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(*):=20go=20=E5=90=8E=E7=AB=AF=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E8=84=9A=E6=89=8B=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + README.md | 8 + cmd/cmd.go | 21 ++ config.yaml | 21 ++ docs/docs.go | 291 +++++++++++++++++++++ docs/swagger.json | 267 ++++++++++++++++++++ docs/swagger.yaml | 181 ++++++++++++++ go.mod | 109 ++++++++ go.sum | 430 ++++++++++++++++++++++++++++++++ internal/api/api.go | 25 ++ internal/api/handler/auth.go | 102 ++++++++ internal/api/handler/cateage.go | 20 ++ internal/api/handler/proxy.go | 42 ++++ internal/api/handler/user.go | 35 +++ internal/model/cateage.go | 16 ++ internal/model/model.go | 16 ++ internal/model/site.go | 18 ++ internal/model/user.go | 23 ++ internal/router/router.go | 14 ++ internal/service/cateage.go | 14 ++ internal/service/service.go | 18 ++ internal/service/user.go | 46 ++++ internal/service/utils.go | 19 ++ main.go | 21 ++ pkg/common/rand.go | 18 ++ pkg/common/response.go | 35 +++ pkg/common/sha1.go | 10 + pkg/config/config.go | 130 ++++++++++ pkg/config/types.go | 76 ++++++ pkg/consts/consts.go | 29 +++ pkg/consts/paths.go | 6 + pkg/consts/version.go | 6 + pkg/db/connect.go | 25 ++ pkg/db/db.go | 64 +++++ pkg/db/gorm_logger.go | 81 ++++++ pkg/logger/func.go | 27 ++ pkg/logger/logger.go | 87 +++++++ pkg/utils/jwt.go | 81 ++++++ pkg/utils/utils_test.go | 25 ++ 39 files changed, 2459 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cmd/cmd.go create mode 100644 config.yaml create mode 100644 docs/docs.go create mode 100644 docs/swagger.json create mode 100644 docs/swagger.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/api/api.go create mode 100644 internal/api/handler/auth.go create mode 100644 internal/api/handler/cateage.go create mode 100644 internal/api/handler/proxy.go create mode 100644 internal/api/handler/user.go create mode 100644 internal/model/cateage.go create mode 100644 internal/model/model.go create mode 100644 internal/model/site.go create mode 100644 internal/model/user.go create mode 100644 internal/router/router.go create mode 100644 internal/service/cateage.go create mode 100644 internal/service/service.go create mode 100644 internal/service/user.go create mode 100644 internal/service/utils.go create mode 100644 main.go create mode 100644 pkg/common/rand.go create mode 100644 pkg/common/response.go create mode 100644 pkg/common/sha1.go create mode 100644 pkg/config/config.go create mode 100644 pkg/config/types.go create mode 100644 pkg/consts/consts.go create mode 100644 pkg/consts/paths.go create mode 100644 pkg/consts/version.go create mode 100644 pkg/db/connect.go create mode 100644 pkg/db/db.go create mode 100644 pkg/db/gorm_logger.go create mode 100644 pkg/logger/func.go create mode 100644 pkg/logger/logger.go create mode 100644 pkg/utils/jwt.go create mode 100644 pkg/utils/utils_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d031da --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +data \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f242bf0 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +## Go + Gorm + Gin 后端开发脚手架 + +### 支持库 +- 数据库 +- 配置文件 +- 日志系统 +- `JWT`等工具类 + diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 0000000..6797ca0 --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "fmt" + + "github.com/zhilv666/navsite/internal/router" + "github.com/zhilv666/navsite/pkg/config" + "github.com/zhilv666/navsite/pkg/db" + "github.com/zhilv666/navsite/pkg/logger" +) + +func Execute() { + conf := config.NewConfig() + + logger.Init(conf.Log) + + db.InitDB(conf.Database, logger.GetLogger()) + + r := router.SetupRouter() + r.Run(fmt.Sprintf(":%d", conf.Server.Port)) +} diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..2640fc9 --- /dev/null +++ b/config.yaml @@ -0,0 +1,21 @@ +server: + debug: true + port: 8080 +log: + level: debug + filepath: data\log.log + max_size_mb: 10 + max_age_day: 7 + backups: 3 + compress: true +database: + driver: sqlite + user: "" + password: "" + host: "" + port: 0 + db_name: "" + sqlite_db_path: data\sqlite.db +jwt: + secret_key: r0jkncyd46ptISEe + expire_duration_hour: 2ns diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..d91c297 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,291 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "termsOfService": "http://kmux.cn", + "contact": { + "name": "zhilv", + "url": "www.kmux.cn", + "email": "zhilv666@qq.com" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/auth/login": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "用户登录", + "parameters": [ + { + "description": "登录参数", + "name": "login", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.Req" + } + } + ], + "responses": { + "200": { + "description": "登录成功", + "schema": { + "$ref": "#/definitions/common.Response-handler_LoginResp" + } + }, + "400": { + "description": "登录失败", + "schema": { + "$ref": "#/definitions/common.Response-any" + } + } + } + } + }, + "/api/v1/auth/register": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "用户注册", + "parameters": [ + { + "description": "注册参数", + "name": "register", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.Req" + } + } + ], + "responses": { + "200": { + "description": "注册成功", + "schema": { + "$ref": "#/definitions/common.Response-handler_Req" + } + }, + "400": { + "description": "注册失败", + "schema": { + "$ref": "#/definitions/common.Response-any" + } + } + } + } + }, + "/api/v1/user/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "获取指定 ID 用户", + "parameters": [ + { + "type": "integer", + "description": "用户 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "获取成功", + "schema": { + "$ref": "#/definitions/common.Response-model_User" + } + }, + "400": { + "description": "获取失败", + "schema": { + "$ref": "#/definitions/common.Response-any" + } + } + } + } + } + }, + "definitions": { + "common.Response-any": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": {}, + "msg": { + "type": "string", + "example": "success" + } + } + }, + "common.Response-handler_LoginResp": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "$ref": "#/definitions/handler.LoginResp" + }, + "msg": { + "type": "string", + "example": "success" + } + } + }, + "common.Response-handler_Req": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "$ref": "#/definitions/handler.Req" + }, + "msg": { + "type": "string", + "example": "success" + } + } + }, + "common.Response-model_User": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "$ref": "#/definitions/model.User" + }, + "msg": { + "type": "string", + "example": "success" + } + } + }, + "handler.LoginResp": { + "type": "object", + "properties": { + "token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...zYyfQ.bQeIyXvkOExxD4DAy5Eyjgwj9FbjE-AO6FCLF-YFGVA" + } + } + }, + "handler.Req": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "otp_code": { + "type": "string", + "example": "123456" + }, + "password": { + "type": "string", + "example": "123456" + }, + "username": { + "type": "string", + "example": "admin" + } + } + }, + "model.User": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "disabled": { + "description": "禁用标志", + "type": "boolean" + }, + "email": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_admin": { + "description": "true = 管理员", + "type": "boolean" + }, + "points": { + "description": "积分", + "type": "integer" + }, + "sso_id": { + "description": "OAuth2 唯一标识", + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "username": { + "description": "邮箱注册", + "type": "string" + }, + "verified": { + "description": "是否通过邮箱验证/管理员审核", + "type": "boolean" + }, + "verify_at": { + "description": "验证时间", + "type": "string" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "localhost:8080", + BasePath: "/api/v1", + Schemes: []string{}, + Title: "我的用户管理平台 API", + Description: "这是一个基于 Gin 的用户管理平台后端 API 文档", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..c51efeb --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,267 @@ +{ + "swagger": "2.0", + "info": { + "description": "这是一个基于 Gin 的用户管理平台后端 API 文档", + "title": "我的用户管理平台 API", + "termsOfService": "http://kmux.cn", + "contact": { + "name": "zhilv", + "url": "www.kmux.cn", + "email": "zhilv666@qq.com" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + }, + "version": "1.0" + }, + "host": "localhost:8080", + "basePath": "/api/v1", + "paths": { + "/api/v1/auth/login": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "用户登录", + "parameters": [ + { + "description": "登录参数", + "name": "login", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.Req" + } + } + ], + "responses": { + "200": { + "description": "登录成功", + "schema": { + "$ref": "#/definitions/common.Response-handler_LoginResp" + } + }, + "400": { + "description": "登录失败", + "schema": { + "$ref": "#/definitions/common.Response-any" + } + } + } + } + }, + "/api/v1/auth/register": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "用户注册", + "parameters": [ + { + "description": "注册参数", + "name": "register", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.Req" + } + } + ], + "responses": { + "200": { + "description": "注册成功", + "schema": { + "$ref": "#/definitions/common.Response-handler_Req" + } + }, + "400": { + "description": "注册失败", + "schema": { + "$ref": "#/definitions/common.Response-any" + } + } + } + } + }, + "/api/v1/user/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "获取指定 ID 用户", + "parameters": [ + { + "type": "integer", + "description": "用户 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "获取成功", + "schema": { + "$ref": "#/definitions/common.Response-model_User" + } + }, + "400": { + "description": "获取失败", + "schema": { + "$ref": "#/definitions/common.Response-any" + } + } + } + } + } + }, + "definitions": { + "common.Response-any": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": {}, + "msg": { + "type": "string", + "example": "success" + } + } + }, + "common.Response-handler_LoginResp": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "$ref": "#/definitions/handler.LoginResp" + }, + "msg": { + "type": "string", + "example": "success" + } + } + }, + "common.Response-handler_Req": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "$ref": "#/definitions/handler.Req" + }, + "msg": { + "type": "string", + "example": "success" + } + } + }, + "common.Response-model_User": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 0 + }, + "data": { + "$ref": "#/definitions/model.User" + }, + "msg": { + "type": "string", + "example": "success" + } + } + }, + "handler.LoginResp": { + "type": "object", + "properties": { + "token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...zYyfQ.bQeIyXvkOExxD4DAy5Eyjgwj9FbjE-AO6FCLF-YFGVA" + } + } + }, + "handler.Req": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "otp_code": { + "type": "string", + "example": "123456" + }, + "password": { + "type": "string", + "example": "123456" + }, + "username": { + "type": "string", + "example": "admin" + } + } + }, + "model.User": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "disabled": { + "description": "禁用标志", + "type": "boolean" + }, + "email": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_admin": { + "description": "true = 管理员", + "type": "boolean" + }, + "points": { + "description": "积分", + "type": "integer" + }, + "sso_id": { + "description": "OAuth2 唯一标识", + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "username": { + "description": "邮箱注册", + "type": "string" + }, + "verified": { + "description": "是否通过邮箱验证/管理员审核", + "type": "boolean" + }, + "verify_at": { + "description": "验证时间", + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..26daaa0 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,181 @@ +basePath: /api/v1 +definitions: + common.Response-any: + properties: + code: + example: 0 + type: integer + data: {} + msg: + example: success + type: string + type: object + common.Response-handler_LoginResp: + properties: + code: + example: 0 + type: integer + data: + $ref: '#/definitions/handler.LoginResp' + msg: + example: success + type: string + type: object + common.Response-handler_Req: + properties: + code: + example: 0 + type: integer + data: + $ref: '#/definitions/handler.Req' + msg: + example: success + type: string + type: object + common.Response-model_User: + properties: + code: + example: 0 + type: integer + data: + $ref: '#/definitions/model.User' + msg: + example: success + type: string + type: object + handler.LoginResp: + properties: + token: + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...zYyfQ.bQeIyXvkOExxD4DAy5Eyjgwj9FbjE-AO6FCLF-YFGVA + type: string + type: object + handler.Req: + properties: + otp_code: + example: "123456" + type: string + password: + example: "123456" + type: string + username: + example: admin + type: string + required: + - password + - username + type: object + model.User: + properties: + created_at: + type: string + disabled: + description: 禁用标志 + type: boolean + email: + type: string + id: + type: integer + is_admin: + description: true = 管理员 + type: boolean + points: + description: 积分 + type: integer + sso_id: + description: OAuth2 唯一标识 + type: string + updated_at: + type: string + username: + description: 邮箱注册 + type: string + verified: + description: 是否通过邮箱验证/管理员审核 + type: boolean + verify_at: + description: 验证时间 + type: string + type: object +host: localhost:8080 +info: + contact: + email: zhilv666@qq.com + name: zhilv + url: www.kmux.cn + description: 这是一个基于 Gin 的用户管理平台后端 API 文档 + license: + name: MIT + url: https://opensource.org/licenses/MIT + termsOfService: http://kmux.cn + title: 我的用户管理平台 API + version: "1.0" +paths: + /api/v1/auth/login: + post: + consumes: + - application/json + parameters: + - description: 登录参数 + in: body + name: login + required: true + schema: + $ref: '#/definitions/handler.Req' + produces: + - application/json + responses: + "200": + description: 登录成功 + schema: + $ref: '#/definitions/common.Response-handler_LoginResp' + "400": + description: 登录失败 + schema: + $ref: '#/definitions/common.Response-any' + summary: 用户登录 + /api/v1/auth/register: + post: + consumes: + - application/json + parameters: + - description: 注册参数 + in: body + name: register + required: true + schema: + $ref: '#/definitions/handler.Req' + produces: + - application/json + responses: + "200": + description: 注册成功 + schema: + $ref: '#/definitions/common.Response-handler_Req' + "400": + description: 注册失败 + schema: + $ref: '#/definitions/common.Response-any' + summary: 用户注册 + /api/v1/user/{id}: + get: + consumes: + - application/json + parameters: + - description: 用户 ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: 获取成功 + schema: + $ref: '#/definitions/common.Response-model_User' + "400": + description: 获取失败 + schema: + $ref: '#/definitions/common.Response-any' + summary: 获取指定 ID 用户 +swagger: "2.0" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..af3189d --- /dev/null +++ b/go.mod @@ -0,0 +1,109 @@ +module github.com/zhilv666/navsite + +go 1.24.6 + +require ( + github.com/fsnotify/fsnotify v1.9.0 + github.com/gin-gonic/gin v1.11.0 + github.com/glebarez/sqlite v1.11.0 + github.com/go-yaml/yaml v2.1.0+incompatible + github.com/spf13/viper v1.21.0 + go.uber.org/zap v1.27.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 + gorm.io/driver/mysql v1.6.0 + gorm.io/driver/postgres v1.6.0 + gorm.io/driver/sqlserver v1.6.1 + gorm.io/gorm v1.31.0 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.2.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.14.2 // indirect + github.com/bytedance/sonic/loader v0.4.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.11 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/spec v0.22.0 // indirect + github.com/go-openapi/swag v0.25.1 // indirect + github.com/go-openapi/swag/conv v0.25.1 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/swag/jsonutils v0.25.1 // indirect + github.com/go-openapi/swag/loading v0.25.1 // indirect + github.com/go-openapi/swag/stringutils v0.25.1 // indirect + github.com/go-openapi/swag/typeutils v0.25.1 // indirect + github.com/go-openapi/swag/yamlutils v0.25.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.28.0 // indirect + github.com/go-sql-driver/mysql v1.8.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.18.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // 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.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/microsoft/go-mssqldb v1.8.2 // 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.4 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.55.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.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/swaggo/files v1.0.1 // indirect + github.com/swaggo/gin-swagger v1.6.1 // indirect + github.com/swaggo/swag v1.16.6 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + github.com/urfave/cli/v2 v2.27.7 // indirect + github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect + go.uber.org/mock v0.6.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/arch v0.22.0 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/tools v0.38.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/yaml.v2 v2.4.0 // 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 + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..70a35f8 --- /dev/null +++ b/go.sum @@ -0,0 +1,430 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +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/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +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/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28= +github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +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.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= +github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= +github.com/bytedance/sonic/loader v0.4.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/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= +github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +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/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/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/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.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= +github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +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.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +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-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/spec v0.22.0 h1:xT/EsX4frL3U09QviRIZXvkh80yibxQmtoEvyqug0Tw= +github.com/go-openapi/spec v0.22.0/go.mod h1:K0FhKxkez8YNS94XzF8YKEMULbFrRw4m15i2YUht4L0= +github.com/go-openapi/swag v0.25.1 h1:6uwVsx+/OuvFVPqfQmOOPsqTcm5/GkBhNwLqIR916n8= +github.com/go-openapi/swag v0.25.1/go.mod h1:bzONdGlT0fkStgGPd3bhZf1MnuPkf2YAys6h+jZipOo= +github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0= +github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8= +github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo= +github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw= +github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc= +github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw= +github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg= +github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA= +github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= +github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= +github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= +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.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= +github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +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/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +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.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +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/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/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/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/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-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/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/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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/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.8.2 h1:236sewazvC8FvG6Dr3bszrVhMkAl4KYImryLkRMCd0I= +github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +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/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/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= +github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= +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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +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/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +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/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +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/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/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.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY= +github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw= +github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= +github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= +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.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +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/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= +github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= +github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= +github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +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.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.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.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +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.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-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.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.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +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-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.1.0/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.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +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.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +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.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.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.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.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +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/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +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/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/driver/sqlserver v1.6.1 h1:XWISFsu2I2pqd1KJhhTZNJMx1jNQ+zVL/Q8ovDcUjtY= +gorm.io/driver/sqlserver v1.6.1/go.mod h1:VZeNn7hqX1aXoN5TPAFGWvxWG90xtA8erGn2gQmpc6U= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY= +gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +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= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/api/api.go b/internal/api/api.go new file mode 100644 index 0000000..d2a0cca --- /dev/null +++ b/internal/api/api.go @@ -0,0 +1,25 @@ +package api + +import ( + "github.com/gin-gonic/gin" + swaggerfiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + docs "github.com/zhilv666/navsite/docs" + "github.com/zhilv666/navsite/internal/api/handler" + "github.com/zhilv666/navsite/internal/model" +) + +func RegisterRouter(r *gin.Engine) { + model.Init() + docs.SwaggerInfo.BasePath = "/" + r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) + + v1 := r.Group("/api/v1") + { + handler.RegisterRouterAuth(v1.Group("/auth")) + handler.RegisterRouterUser(v1.Group("/user")) + handler.RegisterRouterCateage(v1.Group("/cateage")) + v1.GET("/proxy", handler.Proxy) + } + +} diff --git a/internal/api/handler/auth.go b/internal/api/handler/auth.go new file mode 100644 index 0000000..0b0810f --- /dev/null +++ b/internal/api/handler/auth.go @@ -0,0 +1,102 @@ +package handler + +import ( + "errors" + "github.com/gin-gonic/gin" + "github.com/zhilv666/navsite/internal/model" + "github.com/zhilv666/navsite/internal/service" + "github.com/zhilv666/navsite/pkg/common" + "github.com/zhilv666/navsite/pkg/utils" + "gorm.io/gorm" +) + +type Req struct { + Username string `json:"username" binding:"required" example:"admin"` + Password string `json:"password" binding:"required" example:"123456"` + OtpCode string `json:"otp_code" example:"123456"` +} + +type LoginResp struct { + Token string `json:"token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...zYyfQ.bQeIyXvkOExxD4DAy5Eyjgwj9FbjE-AO6FCLF-YFGVA"` +} + +// login +// @Summary 用户登录 +// @Tag Auth +// @Accept json +// @Produce json +// @Param login body Req true "登录参数" +// @Success 200 {object} common.Response[LoginResp] "登录成功" +// @Failure 400 {object} common.Response[any] "登录失败" +// @Router /api/v1/auth/login [post] +func login(c *gin.Context) { + var req Req + if err := c.ShouldBindJSON(&req); err != nil { + common.Fail(c, err.Error()) + return + } + user, err := service.GetUserByName(req.Username) + if err != nil { + common.Fail(c, err.Error()) + return + } + if user == nil { + common.Fail(c, "账号或密码不正确") + return + } + if len(req.Password) < 32 { + req.Password = common.Sha1(req.Password) + } + if user.Password != req.Password { + common.Fail(c, "账号或密码不正确") + return + } + token := utils.GenerateJwtToken(user.ID, user.Email, user.SsoID) + common.Succ(c, LoginResp{Token: token}) +} + +// register +// @Summary 用户注册 +// @Tag Auth +// @Accept json +// @Produce json +// @Param register body Req true "注册参数" +// @Success 200 {object} common.Response[Req] "注册成功" +// @Failure 400 {object} common.Response[any] "注册失败" +// @Router /api/v1/auth/register [post] +func register(c *gin.Context) { + var req Req + if err := c.ShouldBindJSON(&req); err != nil { + common.Fail(c, err.Error()) + return + } + user, err := service.GetUserByName(req.Username) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + } else { + common.Fail(c, "查询用户失败") + return + } + } + if user != nil { + common.Fail(c, "该账号已注册") + return + } + if len(req.Password) < 32 { + req.Password = common.Sha1(req.Password) + } + err = service.AddUser(&model.User{ + Username: req.Username, + Password: req.Password, + }) + if err != nil { + common.Fail(c, err.Error()) + return + } + common.Ok(c, "账号注册成功") +} + +func RegisterRouterAuth(g *gin.RouterGroup) { + g.POST("/login", login) + g.POST("/register", register) +} diff --git a/internal/api/handler/cateage.go b/internal/api/handler/cateage.go new file mode 100644 index 0000000..e878f88 --- /dev/null +++ b/internal/api/handler/cateage.go @@ -0,0 +1,20 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/zhilv666/navsite/internal/service" + "github.com/zhilv666/navsite/pkg/common" +) + +func GetAllCateageWithSites(c *gin.Context) { + cateagies, err := service.GetAllCateageWithSites() + if err != nil { + common.Fail(c, err.Error()) + return + } + common.Succ(c, cateagies) +} + +func RegisterRouterCateage(g *gin.RouterGroup) { + g.GET("/", GetAllCateageWithSites) +} diff --git a/internal/api/handler/proxy.go b/internal/api/handler/proxy.go new file mode 100644 index 0000000..a55d733 --- /dev/null +++ b/internal/api/handler/proxy.go @@ -0,0 +1,42 @@ +package handler + +import ( + "io" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/zhilv666/navsite/pkg/common" +) + +func Proxy(c *gin.Context) { + _url := c.Query("url") + if _url == "" { + common.Fail(c, "missing url") + return + } + // 请求目标 URL + resp, err := http.Get(_url) + if err != nil { + common.Fail(c, "unable to fetch image") + return + } + defer resp.Body.Close() + + // 检查 Content-Type 是否为图片 + contentType := resp.Header.Get("Content-Type") + if !strings.HasPrefix(contentType, "image/") { + common.Fail(c, "not an image file") + return + } + + // 设置响应 Content-Type + c.Writer.Header().Set("Content-Type", contentType) + + // 将图片数据直接写入响应 + _, err = io.Copy(c.Writer, resp.Body) + if err != nil { + common.Fail(c, "failed to write image") + return + } +} diff --git a/internal/api/handler/user.go b/internal/api/handler/user.go new file mode 100644 index 0000000..b78970c --- /dev/null +++ b/internal/api/handler/user.go @@ -0,0 +1,35 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/zhilv666/navsite/internal/service" + "github.com/zhilv666/navsite/pkg/common" + "strconv" +) + +// getUserByID +// @Summary 获取指定 ID 用户 +// @Tag User +// @Accept json +// @Produce json +// @Param id path int true "用户 ID" +// @Success 200 {object} common.Response[model.User] "获取成功" +// @Failure 400 {object} common.Response[any] "获取失败" +// @Router /api/v1/user/{id} [get] +func getUserByID(c *gin.Context) { + id := c.Param("id") + idInt, err := strconv.Atoi(id) + if err != nil { + common.Fail(c, err.Error()) + } + user, err := service.GetUserByID(uint(idInt)) + if err != nil { + common.Fail(c, err.Error()) + return + } + common.Succ(c, user) +} + +func RegisterRouterUser(g *gin.RouterGroup) { + g.GET(":id", getUserByID) +} diff --git a/internal/model/cateage.go b/internal/model/cateage.go new file mode 100644 index 0000000..9493658 --- /dev/null +++ b/internal/model/cateage.go @@ -0,0 +1,16 @@ +package model + +import ( + "time" +) + +type Cateage struct { + ID uint `gorm:"primaryKey" json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt *time.Time `gorm:"index" json:"-"` + + AnchorId string `gorm:"size:100;not null;unique" json:"anchor_id"` + Name string `gorm:"size:100;not null;unique" json:"name"` + Sites []Site `gorm:"foreignKey:CateageID" json:"sites"` // 方便获取分类下所有网站 +} diff --git a/internal/model/model.go b/internal/model/model.go new file mode 100644 index 0000000..efec4c5 --- /dev/null +++ b/internal/model/model.go @@ -0,0 +1,16 @@ +package model + +import ( + "github.com/zhilv666/navsite/pkg/db" + "github.com/zhilv666/navsite/pkg/logger" + "go.uber.org/zap" +) + +func Init() { + err := db.GetDB().AutoMigrate( + new(User), new(Cateage), new(Site), + ) + if err != nil { + logger.Error("database migrate error", zap.Stack("migrate")) + } +} diff --git a/internal/model/site.go b/internal/model/site.go new file mode 100644 index 0000000..aa87ae0 --- /dev/null +++ b/internal/model/site.go @@ -0,0 +1,18 @@ +package model + +import "time" + +type Site struct { + ID uint `gorm:"primaryKey" json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt *time.Time `gorm:"index" json:"-"` + + Name string `gorm:"size:100;not null;unique" json:"name"` + Describe string `gorm:"size:100" json:"describe"` + CateageID uint `gorm:"not null;index" json:"cateage_id"` + Cateage Cateage `gorm:"foreignKey:CateageID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"cateage"` // 外键关联 + Url string `gorm:"size:500;not null" json:"url"` + Cors bool `gorm:"default:false" json:"cors"` + Icon string `gorm:"size:500;default:'https://cos.kmux.cn/md/20251115174627218.png'" json:"icon"` +} diff --git a/internal/model/user.go b/internal/model/user.go new file mode 100644 index 0000000..132c4d0 --- /dev/null +++ b/internal/model/user.go @@ -0,0 +1,23 @@ +package model + +import ( + "time" +) + +type User struct { + ID uint `gorm:"primarykey" json:"id"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` + DeletedAt *time.Time `gorm:"index" json:"-"` + + Username string `gorm:"uniqueIndex;size:50;not null" json:"username"` // 邮箱注册 + Email string `gorm:"uniqueIndex;size:100;not null" json:"email"` + Password string `json:"-" gorm:"size:100;not null"` // SHA1 密码 + Salt string `json:"-" gorm:"size:32;not null"` // 密码盐 + IsAdmin bool `json:"is_admin"` // true = 管理员 + Disabled bool `json:"disabled" gorm:"default:false"` // 禁用标志 + SsoID string `json:"sso_id" gorm:"size:100;unique"` // OAuth2 唯一标识 + Verified bool `json:"verified" gorm:"default:false"` // 是否通过邮箱验证/管理员审核 + VerifyAt *time.Time `json:"verify_at,omitempty"` // 验证时间 + Points int `json:"points" gorm:"default:0"` // 积分 +} diff --git a/internal/router/router.go b/internal/router/router.go new file mode 100644 index 0000000..f734924 --- /dev/null +++ b/internal/router/router.go @@ -0,0 +1,14 @@ +package router + +import ( + "github.com/gin-gonic/gin" + "github.com/zhilv666/navsite/internal/api" +) + +func SetupRouter() *gin.Engine { + r := gin.Default() + + api.RegisterRouter(r) + + return r +} diff --git a/internal/service/cateage.go b/internal/service/cateage.go new file mode 100644 index 0000000..6b2f0d6 --- /dev/null +++ b/internal/service/cateage.go @@ -0,0 +1,14 @@ +package service + +import ( + "github.com/pkg/errors" + "github.com/zhilv666/navsite/internal/model" +) + +func GetAllCateageWithSites() ([]model.Cateage, error) { + var cateagies []model.Cateage + if err := DB().Preload("Sites").Find(&cateagies).Error; err != nil { + return nil, errors.Wrap(err, "failed get cateagies") + } + return cateagies, nil +} diff --git a/internal/service/service.go b/internal/service/service.go new file mode 100644 index 0000000..5668d2c --- /dev/null +++ b/internal/service/service.go @@ -0,0 +1,18 @@ +package service + +import ( + "github.com/zhilv666/navsite/pkg/config" + "github.com/zhilv666/navsite/pkg/db" + "github.com/zhilv666/navsite/pkg/logger" + "gorm.io/gorm" +) + +func DB() *gorm.DB { + _db := db.GetDB() + if _db == nil { + db.InitDB(config.GetConfig().Database, logger.GetLogger()) + return db.GetDB() + } + + return _db +} diff --git a/internal/service/user.go b/internal/service/user.go new file mode 100644 index 0000000..bc2a454 --- /dev/null +++ b/internal/service/user.go @@ -0,0 +1,46 @@ +package service + +import ( + "github.com/pkg/errors" + + "github.com/zhilv666/navsite/internal/model" +) + +func GetUserByID(id uint) (*model.User, error) { + var user model.User + if err := DB().First(&user, id).Error; err != nil { + return nil, errors.Wrapf(err, "failed get old user") + } + return &user, nil +} + +func GetUserByName(username string) (*model.User, error) { + user := model.User{Username: username} + if err := DB().Where(user).First(&user).Error; err != nil { + return nil, errors.Wrapf(err, "failed get old user") + } + return &user, nil +} + +func GetUsers(pageIndex, pageSize int) (users []*model.User, count int64, err error) { + userDB := DB().Model(&model.User{}) + if err := userDB.Count(&count).Error; err != nil { + return nil, 0, errors.Wrapf(err, "failed get counts") + } + if err := userDB.Order(columnName("id")).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&users).Error; err != nil { + return nil, 0, errors.Wrapf(err, "failed get users") + } + return users, count, nil +} + +func AddUser(user *model.User) error { + return errors.WithStack(DB().Create(&user).Error) +} + +func UpdateUser(user *model.User) error { + return errors.WithStack(DB().Save(&user).Error) +} + +func DeleteUser(id uint) error { + return errors.WithStack(DB().Delete(&model.User{}, id).Error) +} diff --git a/internal/service/utils.go b/internal/service/utils.go new file mode 100644 index 0000000..a61191c --- /dev/null +++ b/internal/service/utils.go @@ -0,0 +1,19 @@ +package service + +import ( + "fmt" + + "github.com/zhilv666/navsite/pkg/config" + "gorm.io/gorm" +) + +func columnName(name string) string { + if config.GetConfig().Database.Driver == "postgres" { + return fmt.Sprintf(`"%s"`, name) + } + return fmt.Sprintf("`%s`", name) +} + +func addStorageOrder(db *gorm.DB) *gorm.DB { + return DB().Order(fmt.Sprintf("%s, %s", columnName("order"), columnName("id"))) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..bc876c6 --- /dev/null +++ b/main.go @@ -0,0 +1,21 @@ +package main + +import "github.com/zhilv666/navsite/cmd" + +// @title 我的用户管理平台 API +// @version 1.0 +// @description 这是一个基于 Gin 的用户管理平台后端 API 文档 +// @termsOfService http://kmux.cn + +// @contact.name zhilv +// @contact.url www.kmux.cn +// @contact.email zhilv666@qq.com + +// @license.name MIT +// @license.url https://opensource.org/licenses/MIT + +// @host localhost:8080 +// @BasePath /api/v1 +func main() { + cmd.Execute() +} diff --git a/pkg/common/rand.go b/pkg/common/rand.go new file mode 100644 index 0000000..a54b961 --- /dev/null +++ b/pkg/common/rand.go @@ -0,0 +1,18 @@ +package common + +import ( + "math/rand" + "time" +) + +// Rand 生成指定长度的随机字符串,字符集为 [0-9a-zA-Z] +func Rand(length int) string { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + + seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) + b := make([]byte, length) + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] + } + return string(b) +} diff --git a/pkg/common/response.go b/pkg/common/response.go new file mode 100644 index 0000000..1df7fef --- /dev/null +++ b/pkg/common/response.go @@ -0,0 +1,35 @@ +package common + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +type Response[T any] struct { + Code int `json:"code" example:"0"` + Msg string `json:"msg" example:"success"` + Data T `json:"data,omitempty"` +} + +func Succ(c *gin.Context, data interface{}) { + c.JSON(http.StatusOK, Response[interface{}]{ + Code: 0, + Msg: "success", + Data: data, + }) +} + +func Ok(c *gin.Context, msg string) { + c.JSON(http.StatusOK, Response[interface{}]{ + Code: 0, + Msg: msg, + }) +} + +func Fail(c *gin.Context, msg string) { + c.JSON(http.StatusBadRequest, Response[interface{}]{ + Code: -1, + Msg: msg, + }) +} diff --git a/pkg/common/sha1.go b/pkg/common/sha1.go new file mode 100644 index 0000000..a1bb8e9 --- /dev/null +++ b/pkg/common/sha1.go @@ -0,0 +1,10 @@ +package common + +import ( + "crypto/sha1" + "encoding/hex" +) + +func Sha1(pwd string) string { + return hex.EncodeToString(sha1.New().Sum([]byte(pwd))) +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..4561c28 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,130 @@ +package config + +import ( + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + "sync" + + "github.com/go-yaml/yaml" + "github.com/spf13/viper" + "github.com/zhilv666/navsite/pkg/consts" +) + +var ( + globalViper *viper.Viper + globalViperOnce sync.Once + configMu sync.RWMutex + cachedConfig *Config +) + +// NewConfig 初始化配置 +func NewConfig() *Config { + globalViperOnce.Do(func() { + globalViper = viper.New() + }) + + configPath := loadConfigFile() + if configPath == "" { + log.Println("⚠️ 未找到配置文件,使用默认配置并导出为 config.yaml") + cachedConfig = DefaultConfig() + exportConfig(cachedConfig, "yaml") + return cachedConfig + } + + conf, err := parseConfig() + if err != nil { + log.Printf("⚠️ 解析配置失败,使用默认配置并导出为 config.yaml: %v", err) + conf = DefaultConfig() + exportConfig(conf, "yaml") + } + + cachedConfig = conf + return conf +} + +// loadConfigFile 加载配置文件 +func loadConfigFile() string { + files := []string{ + fmt.Sprintf("%s.yaml", consts.ConfigName), + fmt.Sprintf("%s.yml", consts.ConfigName), + fmt.Sprintf("%s.json", consts.ConfigName), + } + + for _, file := range files { + if _, err := os.Stat(file); err == nil { + ext := filepath.Ext(file)[1:] + globalViper.SetConfigFile(file) + globalViper.SetConfigType(ext) + globalViper.AddConfigPath(".") + + if err := globalViper.ReadInConfig(); err != nil { + log.Printf("⚠️ 读取配置文件 %s 失败: %v", file, err) + continue + } + + log.Printf("✅ 成功加载配置文件: %s", file) + return file + } + } + return "" +} + +// parseConfig 将配置解析为结构体 +func parseConfig() (*Config, error) { + configMu.Lock() + defer configMu.Unlock() + + var conf Config + if err := globalViper.Unmarshal(&conf); err != nil { + return nil, err + } + return &conf, nil +} + +// exportConfig 导出配置文件(默认保存为 config.yaml) +func exportConfig(conf *Config, format string) { + var ( + data []byte + err error + ) + + switch format { + case "json": + data, err = json.MarshalIndent(conf, "", " ") + case "yaml", "yml", "": + format = "yaml" + data, err = yaml.Marshal(conf) + default: + log.Printf("⚠️ 不支持的导出格式: %s", format) + return + } + + if err != nil { + log.Printf("⚠️ 导出 %s 格式配置失败: %v", format, err) + return + } + + filename := fmt.Sprintf("%s.%s", consts.ConfigName, format) + if err := os.WriteFile(filename, data, 0644); err != nil { + log.Printf("⚠️ 写入配置文件 %s 失败: %v", filename, err) + } else { + log.Printf("✅ 已导出配置文件: %s", filename) + } + + log.Printf("📋 当前 %s 配置:\n%s", format, data) +} + +// GetConfig 获取当前配置(线程安全) +func GetConfig() *Config { + configMu.RLock() + defer configMu.RUnlock() + + if cachedConfig == nil { + return DefaultConfig() + } + conf := *cachedConfig + return &conf +} diff --git a/pkg/config/types.go b/pkg/config/types.go new file mode 100644 index 0000000..d85cc1f --- /dev/null +++ b/pkg/config/types.go @@ -0,0 +1,76 @@ +package config + +import ( + "path/filepath" + "time" + + "github.com/zhilv666/navsite/pkg/common" +) + +type Server struct { + Debug bool `mapstructure:"debug"` + Port int `mapstructure:"port"` +} + +type Log struct { + Level string `mapstructure:"level"` + Filepath string `mapstructure:"filepath"` + MaxSizeMB int `mapstructure:"max_size_mb"` // 单个日志文件最大(MB) + MaxAgeDay int `mapstructure:"max_age_day"` // 日志文件最大保存天数 + Backups int `mapstructure:"backups"` // 保留的旧文件个数 + Compress bool `mapstructure:"compress"` // 是否压缩 +} + +type Database struct { + Driver string `mapstructure:"driver"` + User string `mapstructure:"user"` + Password string `mapstructure:"password"` + Host string `mapstructure:"host"` + Port int `mapstructure:"port"` + DbName string `mapstructure:"db_name"` + SqliteDbPath string `mapstructure:"sqlite_db_path"` +} + +type JWT struct { + SecretKey string `mapstructure:"secret_key"` + ExpireDurationHour time.Duration `mapstructure:"expire_duration_hour"` +} + +type Config struct { + Server Server `mapstructure:"server"` + Log Log `mapstructure:"log"` + Database Database `mapstructure:"database"` + JWT JWT `mapstructure:"jwt"` +} + +func DefaultConfig() *Config { + logPath := filepath.Join("data", "log.log") + dbPath := filepath.Join("data", "sqlite.db") + return &Config{ + Server: Server{ + Debug: true, + Port: 8080, + }, + Log: Log{ + Level: "debug", + Filepath: logPath, + MaxSizeMB: 10, + MaxAgeDay: 7, + Backups: 3, + Compress: true, + }, + Database: Database{ + Driver: "sqlite", + User: "", + Password: "", + Host: "", + Port: 0, + DbName: "", + SqliteDbPath: dbPath, + }, + JWT: JWT{ + SecretKey: common.Rand(16), + ExpireDurationHour: 24, + }, + } +} diff --git a/pkg/consts/consts.go b/pkg/consts/consts.go new file mode 100644 index 0000000..9e61c14 --- /dev/null +++ b/pkg/consts/consts.go @@ -0,0 +1,29 @@ +package consts + +import ( + "fmt" +) + +// 通用系统级常量 +const ( + AppName = "NavSite" + AppAuthor = "zhilv666" + AppVersion = "1.0.0" +) + +// 默认服务端口 +const DefaultPort = 8080 + +// 通用时间格式 +const ( + TimeFormatDate = "2006-01-02" + TimeFormatDateTime = "2006-01-02 15:04:05" +) + +func init() { + fmt.Printf("🚀 启动 %s v%s by %s\n", + AppName, + AppVersion, + AppAuthor, + ) +} diff --git a/pkg/consts/paths.go b/pkg/consts/paths.go new file mode 100644 index 0000000..1228c12 --- /dev/null +++ b/pkg/consts/paths.go @@ -0,0 +1,6 @@ +package consts + +// 项目路径与文件定义 +var ( + ConfigName = "config" +) diff --git a/pkg/consts/version.go b/pkg/consts/version.go new file mode 100644 index 0000000..4432dfb --- /dev/null +++ b/pkg/consts/version.go @@ -0,0 +1,6 @@ +package consts + +var ( + BuildTime string + GitCommit string +) diff --git a/pkg/db/connect.go b/pkg/db/connect.go new file mode 100644 index 0000000..f6925a9 --- /dev/null +++ b/pkg/db/connect.go @@ -0,0 +1,25 @@ +package db + +import ( + "github.com/glebarez/sqlite" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlserver" + "gorm.io/gorm" +) + +func connectMySQL(dsn string) gorm.Dialector { + return mysql.Open(dsn) +} + +func connectSQLite(filepath string) gorm.Dialector { + return sqlite.Open(filepath + "?cache=shared&_fk=1&_driver=modernc.org/sqlite") +} + +func connectPostgres(dsn string) gorm.Dialector { + return postgres.Open(dsn) +} + +func connectSQLServer(dsn string) gorm.Dialector { + return sqlserver.Open(dsn) +} diff --git a/pkg/db/db.go b/pkg/db/db.go new file mode 100644 index 0000000..6f15a0e --- /dev/null +++ b/pkg/db/db.go @@ -0,0 +1,64 @@ +package db + +import ( + "fmt" + "os" + + "github.com/zhilv666/navsite/pkg/config" + "go.uber.org/zap" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var db *gorm.DB + +func init() { + if _, err := os.Stat("./data"); os.IsNotExist(err) { + _ = os.Mkdir("./data", os.ModePerm) + } +} + +func GetDB() *gorm.DB { + if db == nil { + panic("数据库未初始化,请先调用 db.InitDB()") + } + return db +} + +func InitDB(cfg config.Database, log *zap.Logger) { + go func() { + if err := recover(); err != nil { + panic(fmt.Sprintf("Init DB Error: %v", err)) + } + }() + + var dsn string + var dialector gorm.Dialector + + switch cfg.Driver { + case "mysql": + dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", + cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DbName) + dialector = connectMySQL(dsn) + case "postgres": + dsn = fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s sslmode=disable", + cfg.Host, cfg.Port, cfg.User, cfg.DbName, cfg.Password) + dialector = connectPostgres(dsn) + case "sqlserver": + dsn = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", + cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DbName) + dialector = connectSQLServer(dsn) + case "sqlite", "sqlite3": + fmt.Println(cfg.SqliteDbPath) + dialector = connectSQLite(cfg.SqliteDbPath) + default: + panic(fmt.Errorf("unsupport database driver: %s", cfg.Driver)) + } + var err error + db, err = gorm.Open(dialector, &gorm.Config{ + Logger: NewZapGormLogger(log).LogMode(logger.Info), + }) + if err != nil { + panic(err) + } +} diff --git a/pkg/db/gorm_logger.go b/pkg/db/gorm_logger.go new file mode 100644 index 0000000..365d9ce --- /dev/null +++ b/pkg/db/gorm_logger.go @@ -0,0 +1,81 @@ +package db + +import ( + "context" + "time" + + "go.uber.org/zap" + "gorm.io/gorm/logger" + "gorm.io/gorm/utils" +) + +type ZapGormLogger struct { + ZapLogger *zap.Logger + LogLevel logger.LogLevel + SlowThreshold time.Duration +} + +func NewZapGormLogger(zapLogger *zap.Logger) *ZapGormLogger { + return &ZapGormLogger{ + ZapLogger: zapLogger, + LogLevel: logger.Info, + SlowThreshold: time.Second, // 1s 慢查询阈值 + } +} + +func (l *ZapGormLogger) LogMode(level logger.LogLevel) logger.Interface { + newlogger := *l + newlogger.LogLevel = level + return &newlogger +} + +func (l *ZapGormLogger) Info(ctx context.Context, msg string, data ...interface{}) { + if l.LogLevel >= logger.Info { + l.ZapLogger.Sugar().Infof(msg, data...) + } +} + +func (l *ZapGormLogger) Warn(ctx context.Context, msg string, data ...interface{}) { + if l.LogLevel >= logger.Warn { + l.ZapLogger.Sugar().Warnf(msg, data...) + } +} + +func (l *ZapGormLogger) Error(ctx context.Context, msg string, data ...interface{}) { + if l.LogLevel >= logger.Error { + l.ZapLogger.Sugar().Errorf(msg, data...) + } +} + +func (l *ZapGormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) { + if l.LogLevel <= logger.Silent { + return + } + elapsed := time.Since(begin) + sql, rows := fc() + + switch { + case err != nil && l.LogLevel >= logger.Error: + l.ZapLogger.Error("SQL Error", + zap.Error(err), + zap.String("sql", sql), + zap.Int64("rows", rows), + zap.Duration("elapsed", elapsed), + zap.String("file", utils.FileWithLineNum()), + ) + case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= logger.Warn: + l.ZapLogger.Warn("Slow SQL", + zap.Duration("elapsed", elapsed), + zap.String("sql", sql), + zap.Int64("rows", rows), + zap.String("file", utils.FileWithLineNum()), + ) + case l.LogLevel >= logger.Info: + l.ZapLogger.Info("SQL", + zap.String("sql", sql), + zap.Int64("rows", rows), + zap.Duration("elapsed", elapsed), + zap.String("file", utils.FileWithLineNum()), + ) + } +} diff --git a/pkg/logger/func.go b/pkg/logger/func.go new file mode 100644 index 0000000..a81c291 --- /dev/null +++ b/pkg/logger/func.go @@ -0,0 +1,27 @@ +package logger + +import "go.uber.org/zap" + +func GetLogger() *zap.Logger { + return logger +} + +func Debug(msg string, fields ...zap.Field) { + logger.Debug(msg, fields...) +} + +func Info(msg string, fields ...zap.Field) { + logger.Info(msg, fields...) +} + +func Warn(msg string, fields ...zap.Field) { + logger.Warn(msg, fields...) +} + +func Error(msg string, fields ...zap.Field) { + logger.Error(msg, fields...) +} + +func Sync() { + _ = logger.Sync() +} diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go new file mode 100644 index 0000000..19e9fe0 --- /dev/null +++ b/pkg/logger/logger.go @@ -0,0 +1,87 @@ +package logger + +import ( + "os" + "time" + + "github.com/zhilv666/navsite/pkg/config" + "github.com/zhilv666/navsite/pkg/consts" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" +) + +var logger *zap.Logger + +func Init(log config.Log) { + var zapLevel zapcore.Level + + // 日志等级解析 + switch log.Level { + case "debug": + zapLevel = zap.DebugLevel + case "info": + zapLevel = zap.InfoLevel + case "warning": + zapLevel = zap.WarnLevel + case "error": + zapLevel = zap.ErrorLevel + default: + zapLevel = zap.InfoLevel + } + + // lumberjack 日志切割配置 + writeSyncer := zapcore.AddSync(&lumberjack.Logger{ + Filename: log.Filepath, + MaxSize: log.MaxSizeMB, + MaxBackups: log.Backups, + MaxAge: log.MaxAgeDay, + Compress: log.Compress, + }) + + // 日志编码格式 + encoderConfigColor := zapcore.EncoderConfig{ + TimeKey: "time", + LevelKey: "level", + NameKey: "logger", + CallerKey: "caller", + MessageKey: "msg", + StacktraceKey: "Stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.CapitalColorLevelEncoder, // 彩色等级输出(终端) + // EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeTime: timeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } + + encoderConfig := zapcore.EncoderConfig{ + TimeKey: "time", + LevelKey: "level", + NameKey: "logger", + CallerKey: "caller", + MessageKey: "msg", + StacktraceKey: "Stacktrace", + LineEnding: zapcore.DefaultLineEnding, + // EncodeLevel: zapcore.CapitalColorLevelEncoder, // 彩色等级输出(终端) + EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeTime: timeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } + + encoderConsole := zapcore.NewConsoleEncoder(encoderConfigColor) + encoderJson := zapcore.NewJSONEncoder(encoderConfig) + + core := zapcore.NewTee( + zapcore.NewCore(encoderJson, writeSyncer, zapLevel), + zapcore.NewCore(encoderConsole, zapcore.AddSync(os.Stdout), zapLevel)) + + logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1)) + zap.ReplaceGlobals(logger) +} + +// 时间格式 +func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendString(t.Format(consts.TimeFormatDateTime)) +} diff --git a/pkg/utils/jwt.go b/pkg/utils/jwt.go new file mode 100644 index 0000000..fc6e004 --- /dev/null +++ b/pkg/utils/jwt.go @@ -0,0 +1,81 @@ +package utils + +import ( + "errors" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/zhilv666/navsite/pkg/config" + "github.com/zhilv666/navsite/pkg/logger" + "go.uber.org/zap" +) + +type MyCustomClaims struct { + ID uint `json:"id"` + Email string `json:"Email"` + SsoID string `json:"sso_id"` + jwt.RegisteredClaims +} + +// GenerateJwtToken 生成 JWT Token +func GenerateJwtToken(id uint, email, sso_id string) string { + jwtConf := config.GetConfig().JWT + claims := MyCustomClaims{ + id, + email, + sso_id, + jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(jwtConf.ExpireDurationHour * time.Hour)), + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + ss, err := token.SignedString([]byte(jwtConf.SecretKey)) + if err != nil { + logger.Error("generate jwt token error", zap.String("jwt", err.Error())) + } + return ss +} + +// ParseJwtToken 解析 JWT 并返回自定义 Claims,同时返回错误信息 +func ParseJwtToken(tokenString string) (*MyCustomClaims, error) { + jwtConf := config.GetConfig().JWT + + token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (any, error) { + return []byte(jwtConf.SecretKey), nil + }, jwt.WithLeeway(5*time.Second)) + + if err != nil { + logger.Error("parse jwt token error", zap.String("jwt", err.Error())) + return nil, err + } + + claims, ok := token.Claims.(*MyCustomClaims) + if !ok || !token.Valid { + logger.Error("invalid jwt claims") + return nil, errors.New("jwt token invalid") + } + + return claims, nil +} + +// ValidJwtToken 验证 JWT Token +func ValidJwtToken(tokenString string) bool { + jwtConf := config.GetConfig().JWT + + token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (any, error) { + return []byte(jwtConf.SecretKey), nil + }, jwt.WithLeeway(5*time.Second)) + + if err != nil { + logger.Error("parse jwt token error", zap.String("jwt", err.Error())) + return false + } + + _, ok := token.Claims.(*MyCustomClaims) + if !ok || !token.Valid { + logger.Error("invalid jwt claims") + return false + } + + return true +} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go new file mode 100644 index 0000000..09b75a0 --- /dev/null +++ b/pkg/utils/utils_test.go @@ -0,0 +1,25 @@ +package utils_test + +import ( + "testing" + + "github.com/zhilv666/navsite/pkg/config" + "github.com/zhilv666/navsite/pkg/logger" + "github.com/zhilv666/navsite/pkg/utils" +) + +func TestJwt(t *testing.T) { + conf := config.NewConfig() + logger.Init(conf.Log) + toekn := utils.GenerateJwtToken(11, "zhilv666@qq.com", "") + t.Log("jwt token: ", toekn) + + claimas, err := utils.ParseJwtToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTEsIkVtYWlsIjoiemhpbHY2NjZAcXEuY29tIiwic3NvX2lkIjoiIiwiZXhwIjoxNzYyMDkxNzYyfQ.bQeIyXvkOExxD4DAy5Eyjgwj9FbjE-AO6FCLF-YFGVA") + if err != nil { + t.Log("parse jwt token err") + return + } + t.Log(claimas) + + t.Log(utils.ValidJwtToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTEsIkVtYWlsIjoiemhpbHY2NjZAcXEuY29tIiwic3NvX2lkIjoiIiwiZXhwIjoxNzYyMDkyNTIxfQ.QynKGZmUSOXGgVVsqf-IMYBb11UPC6DT56p1UaNgHC0")) +}