Compare commits
2 Commits
a4b6c5011e
...
bdd2be5dc5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdd2be5dc5 | ||
|
|
4188ee1555 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -23,7 +23,8 @@ dist/
|
||||
build/
|
||||
|
||||
# Compiled binaries
|
||||
server
|
||||
/server
|
||||
server.exe
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
|
||||
123
cmd/server/main.go
Normal file
123
cmd/server/main.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
_ "carrotskin/docs" // Swagger文档
|
||||
"carrotskin/internal/handler"
|
||||
"carrotskin/internal/middleware"
|
||||
"carrotskin/pkg/auth"
|
||||
"carrotskin/pkg/config"
|
||||
"carrotskin/pkg/database"
|
||||
"carrotskin/pkg/email"
|
||||
"carrotskin/pkg/logger"
|
||||
"carrotskin/pkg/redis"
|
||||
"carrotskin/pkg/storage"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 初始化配置
|
||||
if err := config.Init(); err != nil {
|
||||
log.Fatalf("配置加载失败: %v", err)
|
||||
}
|
||||
cfg := config.MustGetConfig()
|
||||
|
||||
// 初始化日志
|
||||
if err := logger.Init(cfg.Log); err != nil {
|
||||
log.Fatalf("日志初始化失败: %v", err)
|
||||
}
|
||||
loggerInstance := logger.MustGetLogger()
|
||||
defer loggerInstance.Sync()
|
||||
|
||||
// 初始化数据库
|
||||
if err := database.Init(cfg.Database, loggerInstance); err != nil {
|
||||
loggerInstance.Fatal("数据库初始化失败", zap.Error(err))
|
||||
}
|
||||
defer database.Close()
|
||||
|
||||
// 执行数据库迁移
|
||||
if err := database.AutoMigrate(loggerInstance); err != nil {
|
||||
loggerInstance.Fatal("数据库迁移失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 初始化JWT服务
|
||||
if err := auth.Init(cfg.JWT); err != nil {
|
||||
loggerInstance.Fatal("JWT服务初始化失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 初始化Redis
|
||||
if err := redis.Init(cfg.Redis, loggerInstance); err != nil {
|
||||
loggerInstance.Fatal("Redis连接失败", zap.Error(err))
|
||||
}
|
||||
defer redis.MustGetClient().Close()
|
||||
|
||||
// 初始化对象存储 (RustFS - S3兼容)
|
||||
// 如果对象存储未配置或连接失败,记录警告但不退出(某些功能可能不可用)
|
||||
if err := storage.Init(cfg.RustFS); err != nil {
|
||||
loggerInstance.Warn("对象存储连接失败,某些功能可能不可用", zap.Error(err))
|
||||
} else {
|
||||
loggerInstance.Info("对象存储连接成功")
|
||||
}
|
||||
|
||||
// 初始化邮件服务
|
||||
if err := email.Init(cfg.Email, loggerInstance); err != nil {
|
||||
loggerInstance.Fatal("邮件服务初始化失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 设置Gin模式
|
||||
if cfg.Server.Mode == "production" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
// 创建路由
|
||||
router := gin.New()
|
||||
|
||||
// 添加中间件
|
||||
router.Use(middleware.Logger(loggerInstance))
|
||||
router.Use(middleware.Recovery(loggerInstance))
|
||||
router.Use(middleware.CORS())
|
||||
|
||||
// 注册路由
|
||||
handler.RegisterRoutes(router)
|
||||
|
||||
// 创建HTTP服务器
|
||||
srv := &http.Server{
|
||||
Addr: cfg.Server.Port,
|
||||
Handler: router,
|
||||
ReadTimeout: cfg.Server.ReadTimeout,
|
||||
WriteTimeout: cfg.Server.WriteTimeout,
|
||||
}
|
||||
|
||||
// 启动服务器
|
||||
go func() {
|
||||
loggerInstance.Info("服务器启动", zap.String("port", cfg.Server.Port))
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
loggerInstance.Fatal("服务器启动失败", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// 等待中断信号优雅关闭
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
loggerInstance.Info("正在关闭服务器...")
|
||||
|
||||
// 设置关闭超时
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
loggerInstance.Fatal("服务器强制关闭", zap.Error(err))
|
||||
}
|
||||
|
||||
loggerInstance.Info("服务器已关闭")
|
||||
}
|
||||
17
go.mod
17
go.mod
@@ -9,6 +9,7 @@ require (
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/minio/minio-go/v7 v7.0.66
|
||||
github.com/redis/go-redis/v9 v9.0.5
|
||||
github.com/spf13/viper v1.21.0
|
||||
@@ -17,13 +18,19 @@ require (
|
||||
github.com/wenlng/go-captcha-assets v1.0.7
|
||||
github.com/wenlng/go-captcha/v2 v2.0.4
|
||||
go.uber.org/zap v1.26.0
|
||||
gorm.io/driver/postgres v1.5.4
|
||||
gorm.io/gorm v1.25.5
|
||||
gorm.io/datatypes v1.2.7
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/gorm v1.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
golang.org/x/image v0.16.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
gorm.io/driver/mysql v1.5.6 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -47,10 +54,10 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.15.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.4.3
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.6.0
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
|
||||
46
go.sum
46
go.sum
@@ -1,3 +1,5 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
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.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
@@ -54,12 +56,19 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM=
|
||||
github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
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/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
@@ -67,14 +76,16 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.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/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||
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/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=
|
||||
@@ -102,12 +113,18 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
|
||||
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw=
|
||||
@@ -254,8 +271,17 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
|
||||
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/datatypes v1.2.7 h1:ww9GAhF1aGXZY3EB3cJPJ7//JiuQo7DlQA7NNlVaTdk=
|
||||
gorm.io/datatypes v1.2.7/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY=
|
||||
gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
|
||||
gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
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/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
|
||||
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||
gorm.io/driver/sqlserver v1.6.0 h1:VZOBQVsVhkHU/NzNhRJKoANt5pZGQAS1Bwc6m6dgfnc=
|
||||
gorm.io/driver/sqlserver v1.6.0/go.mod h1:WQzt4IJo/WHKnckU9jXBLMJIVNMVeTu25dnOzehntWw=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
@@ -37,6 +37,9 @@ func RegisterRoutes(router *gin.Engine) {
|
||||
|
||||
// 更换邮箱
|
||||
userGroup.POST("/change-email", ChangeEmail)
|
||||
|
||||
// Yggdrasil密码相关
|
||||
userGroup.POST("/yggdrasil-password/reset", ResetYggdrasilPassword) // 重置Yggdrasil密码并返回新密码
|
||||
}
|
||||
|
||||
// 材质路由
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"carrotskin/internal/service"
|
||||
"carrotskin/internal/types"
|
||||
"carrotskin/pkg/config"
|
||||
"carrotskin/pkg/database"
|
||||
"carrotskin/pkg/logger"
|
||||
"carrotskin/pkg/redis"
|
||||
"carrotskin/pkg/storage"
|
||||
@@ -413,3 +414,49 @@ func ChangeEmail(c *gin.Context) {
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}))
|
||||
}
|
||||
|
||||
// ResetYggdrasilPassword 重置Yggdrasil密码
|
||||
// @Summary 重置Yggdrasil密码
|
||||
// @Description 重置当前用户的Yggdrasil密码并返回新密码
|
||||
// @Tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} model.Response "重置成功"
|
||||
// @Failure 401 {object} model.ErrorResponse "未授权"
|
||||
// @Failure 500 {object} model.ErrorResponse "服务器错误"
|
||||
// @Router /api/v1/user/yggdrasil-password/reset [post]
|
||||
func ResetYggdrasilPassword(c *gin.Context) {
|
||||
loggerInstance := logger.MustGetLogger()
|
||||
db := database.MustGetDB()
|
||||
|
||||
// 从上下文获取用户ID
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, model.NewErrorResponse(
|
||||
model.CodeUnauthorized,
|
||||
"未授权",
|
||||
nil,
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
userId := userID.(int64)
|
||||
|
||||
// 重置Yggdrasil密码
|
||||
newPassword, err := service.ResetYggdrasilPassword(db, userId)
|
||||
if err != nil {
|
||||
loggerInstance.Error("[ERROR] 重置Yggdrasil密码失败", zap.Error(err), zap.Int64("userId", userId))
|
||||
c.JSON(http.StatusInternalServerError, model.NewErrorResponse(
|
||||
model.CodeServerError,
|
||||
"重置Yggdrasil密码失败",
|
||||
nil,
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
loggerInstance.Info("[INFO] Yggdrasil密码重置成功", zap.Int64("userId", userId))
|
||||
c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{
|
||||
"password": newPassword,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -234,10 +234,8 @@ func Authenticate(c *gin.Context) {
|
||||
response.SelectedProfile = service.SerializeProfile(db, loggerInstance, redisClient, *selectedProfile)
|
||||
}
|
||||
if request.RequestUser {
|
||||
response.User = map[string]interface{}{
|
||||
"id": userId,
|
||||
"properties": user.Properties,
|
||||
}
|
||||
// 使用 SerializeUser 来正确处理 Properties 字段
|
||||
response.User = service.SerializeUser(loggerInstance, user, UUID)
|
||||
}
|
||||
|
||||
// 返回认证响应
|
||||
|
||||
@@ -2,6 +2,8 @@ package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
// User 用户模型
|
||||
@@ -14,7 +16,7 @@ type User struct {
|
||||
Points int `gorm:"column:points;type:integer;not null;default:0" json:"points"`
|
||||
Role string `gorm:"column:role;type:varchar(50);not null;default:'user'" json:"role"`
|
||||
Status int16 `gorm:"column:status;type:smallint;not null;default:1" json:"status"` // 1:正常, 0:禁用, -1:删除
|
||||
Properties string `gorm:"column:properties;type:jsonb" json:"properties"` // JSON字符串,存储为PostgreSQL的JSONB类型
|
||||
Properties *datatypes.JSON `gorm:"column:properties;type:jsonb" json:"properties,omitempty"` // JSON数据,存储为PostgreSQL的JSONB类型
|
||||
LastLoginAt *time.Time `gorm:"column:last_login_at;type:timestamp" json:"last_login_at,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||
|
||||
@@ -14,3 +14,9 @@ func GetYggdrasilPasswordById(Id int64) (string, error) {
|
||||
}
|
||||
return yggdrasil.Password, nil
|
||||
}
|
||||
|
||||
// ResetYggdrasilPassword 重置Yggdrasil密码
|
||||
func ResetYggdrasilPassword(userId int64, newPassword string) error {
|
||||
db := database.MustGetDB()
|
||||
return db.Model(&model.Yggdrasil{}).Where("id = ?", userId).Update("password", newPassword).Error
|
||||
}
|
||||
@@ -129,13 +129,19 @@ func GenerateCaptchaData(ctx context.Context, redisClient *redis.Client) (string
|
||||
redisDataJSON,
|
||||
expireTime,
|
||||
); err != nil {
|
||||
return "", "", "", 0, fmt.Errorf("存储验证码到Redis失败: %w", err)
|
||||
return "", "", "", 0, fmt.Errorf("存储验证码到redis失败: %w", err)
|
||||
}
|
||||
return mBase64, tBase64, captchaID, y - 10, nil
|
||||
}
|
||||
|
||||
// VerifyCaptchaData 验证用户验证码
|
||||
func VerifyCaptchaData(ctx context.Context, redisClient *redis.Client, dx int, id string) (bool, error) {
|
||||
// 测试环境下直接通过验证
|
||||
cfg, err := config.GetConfig()
|
||||
if err == nil && cfg.IsTestEnvironment() {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
redisKey := redisKeyPrefix + id
|
||||
|
||||
// 从Redis获取验证信息,使用注入的客户端
|
||||
@@ -144,11 +150,11 @@ func VerifyCaptchaData(ctx context.Context, redisClient *redis.Client, dx int, i
|
||||
if redisClient.Nil(err) { // 使用封装客户端的Nil错误
|
||||
return false, errors.New("验证码已过期或无效")
|
||||
}
|
||||
return false, fmt.Errorf("Redis查询失败: %w", err)
|
||||
return false, fmt.Errorf("redis查询失败: %w", err)
|
||||
}
|
||||
var redisData RedisData
|
||||
if err := json.Unmarshal([]byte(dataJSON), &redisData); err != nil {
|
||||
return false, fmt.Errorf("解析Redis数据失败: %w", err)
|
||||
return false, fmt.Errorf("解析redis数据失败: %w", err)
|
||||
}
|
||||
tx := redisData.Tx
|
||||
ty := redisData.Ty
|
||||
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"carrotskin/internal/model"
|
||||
"carrotskin/pkg/redis"
|
||||
"encoding/base64"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -91,7 +92,22 @@ func SerializeUser(logger *zap.Logger, u *model.User, UUID string) map[string]in
|
||||
|
||||
data := map[string]interface{}{
|
||||
"id": UUID,
|
||||
"properties": u.Properties,
|
||||
}
|
||||
|
||||
// 正确处理 *datatypes.JSON 指针类型
|
||||
// 如果 Properties 为 nil,则设置为 nil;否则解引用并解析为 JSON 值
|
||||
if u.Properties == nil {
|
||||
data["properties"] = nil
|
||||
} else {
|
||||
// datatypes.JSON 是 []byte 类型,需要解析为实际的 JSON 值
|
||||
var propertiesValue interface{}
|
||||
if err := json.Unmarshal(*u.Properties, &propertiesValue); err != nil {
|
||||
logger.Warn("[WARN] 解析用户Properties失败,使用空值", zap.Error(err))
|
||||
data["properties"] = nil
|
||||
} else {
|
||||
data["properties"] = propertiesValue
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ func TestSerializeUser_ActualCall(t *testing.T) {
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
Properties: "{}",
|
||||
// Properties 使用 datatypes.JSON,测试中可以为空
|
||||
}
|
||||
|
||||
result := SerializeUser(logger, user, "test-uuid-123")
|
||||
|
||||
@@ -50,6 +50,7 @@ func RegisterUser(jwtService *auth.JWTService, username, password, email, avatar
|
||||
Role: "user",
|
||||
Status: 1,
|
||||
Points: 0, // 初始积分可以从配置读取
|
||||
// Properties 字段使用 datatypes.JSON,默认为 nil,数据库会存储 NULL
|
||||
}
|
||||
|
||||
if err := repository.CreateUser(user); err != nil {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"carrotskin/pkg/config"
|
||||
"carrotskin/pkg/email"
|
||||
"carrotskin/pkg/redis"
|
||||
)
|
||||
@@ -39,6 +40,12 @@ func GenerateVerificationCode() (string, error) {
|
||||
|
||||
// SendVerificationCode 发送验证码
|
||||
func SendVerificationCode(ctx context.Context, redisClient *redis.Client, emailService *email.Service, email, codeType string) error {
|
||||
// 测试环境下直接跳过,不存储也不发送
|
||||
cfg, err := config.GetConfig()
|
||||
if err == nil && cfg.IsTestEnvironment() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查发送频率限制
|
||||
rateLimitKey := fmt.Sprintf("verification:rate_limit:%s:%s", codeType, email)
|
||||
exists, err := redisClient.Exists(ctx, rateLimitKey)
|
||||
@@ -78,6 +85,12 @@ func SendVerificationCode(ctx context.Context, redisClient *redis.Client, emailS
|
||||
|
||||
// VerifyCode 验证验证码
|
||||
func VerifyCode(ctx context.Context, redisClient *redis.Client, email, code, codeType string) error {
|
||||
// 测试环境下直接通过验证
|
||||
cfg, err := config.GetConfig()
|
||||
if err == nil && cfg.IsTestEnvironment() {
|
||||
return nil
|
||||
}
|
||||
|
||||
codeKey := fmt.Sprintf("verification:code:%s:%s", codeType, email)
|
||||
|
||||
// 从Redis获取验证码
|
||||
|
||||
@@ -8,11 +8,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -78,6 +79,33 @@ func GetPasswordByUserId(db *gorm.DB, userId int64) (string, error) {
|
||||
return passwordStore, nil
|
||||
}
|
||||
|
||||
// ResetYggdrasilPassword 重置并返回新的Yggdrasil密码
|
||||
func ResetYggdrasilPassword(db *gorm.DB, userId int64) (string, error) {
|
||||
// 生成新的16位随机密码
|
||||
newPassword := model.GenerateRandomPassword(16)
|
||||
|
||||
// 检查Yggdrasil记录是否存在
|
||||
_, err := repository.GetYggdrasilPasswordById(userId)
|
||||
if err != nil {
|
||||
// 如果不存在,创建新记录
|
||||
yggdrasil := model.Yggdrasil{
|
||||
ID: userId,
|
||||
Password: newPassword,
|
||||
}
|
||||
if err := db.Create(&yggdrasil).Error; err != nil {
|
||||
return "", fmt.Errorf("创建Yggdrasil密码失败: %w", err)
|
||||
}
|
||||
return newPassword, nil
|
||||
}
|
||||
|
||||
// 如果存在,更新密码
|
||||
if err := repository.ResetYggdrasilPassword(userId, newPassword); err != nil {
|
||||
return "", fmt.Errorf("重置Yggdrasil密码失败: %w", err)
|
||||
}
|
||||
|
||||
return newPassword, nil
|
||||
}
|
||||
|
||||
// JoinServer 记录玩家加入服务器的会话信息
|
||||
func JoinServer(db *gorm.DB, logger *zap.Logger, redisClient *redis.Client, serverId, accessToken, selectedProfile, ip string) error {
|
||||
// 输入验证
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
// Config 应用配置结构体
|
||||
type Config struct {
|
||||
Environment string `mapstructure:"environment"`
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Database DatabaseConfig `mapstructure:"database"`
|
||||
Redis RedisConfig `mapstructure:"redis"`
|
||||
@@ -301,4 +302,14 @@ func overrideFromEnv(config *Config) {
|
||||
if emailEnabled := os.Getenv("EMAIL_ENABLED"); emailEnabled != "" {
|
||||
config.Email.Enabled = emailEnabled == "true" || emailEnabled == "True" || emailEnabled == "TRUE" || emailEnabled == "1"
|
||||
}
|
||||
|
||||
// 处理环境配置
|
||||
if env := os.Getenv("ENVIRONMENT"); env != "" {
|
||||
config.Environment = env
|
||||
}
|
||||
}
|
||||
|
||||
// IsTestEnvironment 判断是否为测试环境
|
||||
func (c *Config) IsTestEnvironment() bool {
|
||||
return c.Environment == "test"
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ func AutoMigrate(logger *zap.Logger) error {
|
||||
logger.Info("开始执行数据库迁移...")
|
||||
|
||||
// 迁移所有表 - 注意顺序:先创建被引用的表,再创建引用表
|
||||
err = db.AutoMigrate(
|
||||
// 使用分批迁移,避免某些表的问题影响其他表
|
||||
tables := []interface{}{
|
||||
// 用户相关表(先创建,因为其他表可能引用它)
|
||||
&model.User{},
|
||||
&model.UserPointLog{},
|
||||
@@ -87,11 +88,30 @@ func AutoMigrate(logger *zap.Logger) error {
|
||||
|
||||
// Casbin权限规则表
|
||||
&model.CasbinRule{},
|
||||
)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error("数据库迁移失败", zap.Error(err))
|
||||
return fmt.Errorf("数据库迁移失败: %w", err)
|
||||
// 逐个迁移表,以便更好地定位问题
|
||||
for _, table := range tables {
|
||||
tableName := fmt.Sprintf("%T", table)
|
||||
logger.Info("正在迁移表", zap.String("table", tableName))
|
||||
if err := db.AutoMigrate(table); err != nil {
|
||||
logger.Error("数据库迁移失败", zap.Error(err), zap.String("table", tableName))
|
||||
// 如果是 User 表且错误是 insufficient arguments,可能是 Properties 字段问题
|
||||
if tableName == "*model.User" {
|
||||
logger.Warn("User 表迁移失败,可能是 Properties 字段问题,尝试修复...")
|
||||
// 尝试手动添加 properties 字段(如果不存在)
|
||||
if err := db.Exec("ALTER TABLE \"user\" ADD COLUMN IF NOT EXISTS properties jsonb").Error; err != nil {
|
||||
logger.Error("添加 properties 字段失败", zap.Error(err))
|
||||
}
|
||||
// 再次尝试迁移
|
||||
if err := db.AutoMigrate(table); err != nil {
|
||||
return fmt.Errorf("数据库迁移失败 (表: %T): %w", table, err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("数据库迁移失败 (表: %T): %w", table, err)
|
||||
}
|
||||
}
|
||||
logger.Info("表迁移成功", zap.String("table", tableName))
|
||||
}
|
||||
|
||||
logger.Info("数据库迁移完成")
|
||||
|
||||
293
scripts/generate_test_account.py
Normal file
293
scripts/generate_test_account.py
Normal file
@@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
CarrotSkin 测试账户生成器
|
||||
此脚本创建新用户、角色,并输出所有测试信息
|
||||
"""
|
||||
|
||||
import json
|
||||
import random
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
print("错误: 需要安装 requests 库")
|
||||
print("请运行: pip install requests")
|
||||
sys.exit(1)
|
||||
|
||||
# 颜色输出支持(Windows 和 Unix 都支持)
|
||||
try:
|
||||
from colorama import init, Fore, Style
|
||||
init(autoreset=True)
|
||||
HAS_COLORAMA = True
|
||||
# colorama 没有 GRAY,使用 WHITE 代替
|
||||
Fore.GRAY = Fore.WHITE
|
||||
except ImportError:
|
||||
# 如果没有 colorama,使用空字符串
|
||||
class Fore:
|
||||
CYAN = YELLOW = GREEN = RED = GRAY = WHITE = RESET = ""
|
||||
class Style:
|
||||
RESET_ALL = ""
|
||||
HAS_COLORAMA = False
|
||||
|
||||
# API 基础 URL
|
||||
BASE_URL = "http://localhost:8080/api/v1"
|
||||
|
||||
|
||||
def print_colored(text, color=Fore.RESET):
|
||||
"""打印彩色文本"""
|
||||
print(f"{color}{text}{Style.RESET_ALL}")
|
||||
|
||||
|
||||
def print_header(text):
|
||||
"""打印标题"""
|
||||
print_colored(f"\n{'=' * 40}", Fore.CYAN)
|
||||
print_colored(f" {text}", Fore.CYAN)
|
||||
print_colored(f"{'=' * 40}\n", Fore.CYAN)
|
||||
|
||||
|
||||
def print_step(text):
|
||||
"""打印步骤标题"""
|
||||
print_colored(f"\n=== {text} ===", Fore.YELLOW)
|
||||
|
||||
|
||||
def print_success(text):
|
||||
"""打印成功消息"""
|
||||
print_colored(f"✓ {text}", Fore.GREEN)
|
||||
|
||||
|
||||
def print_error(text):
|
||||
"""打印错误消息"""
|
||||
print_colored(f"✗ {text}", Fore.RED)
|
||||
|
||||
|
||||
def print_info(text):
|
||||
"""打印信息消息"""
|
||||
print_colored(f" {text}", Fore.GRAY)
|
||||
|
||||
|
||||
def register_user():
|
||||
"""步骤1: 注册新用户"""
|
||||
print_step("步骤1: 注册新用户")
|
||||
|
||||
random_num = random.randint(10000, 99999)
|
||||
username = f"testuser{random_num}"
|
||||
email = f"test{random_num}@example.com"
|
||||
login_password = "password123"
|
||||
verification_code = "123456"
|
||||
|
||||
print_info(f"用户名: {username}")
|
||||
print_info(f"邮箱: {email}")
|
||||
print_info(f"密码: {login_password}")
|
||||
|
||||
register_data = {
|
||||
"username": username,
|
||||
"email": email,
|
||||
"password": login_password,
|
||||
"verification_code": verification_code
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/auth/register",
|
||||
json=register_data,
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=10
|
||||
)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
if result.get("code") == 200:
|
||||
jwt_token = result["data"]["token"]
|
||||
user_id = result["data"]["user_info"]["id"]
|
||||
user_role = result["data"]["user_info"]["role"]
|
||||
|
||||
print_success("注册成功!")
|
||||
print_info(f"用户ID: {user_id}")
|
||||
print_info(f"角色: {user_role}")
|
||||
|
||||
return {
|
||||
"username": username,
|
||||
"email": email,
|
||||
"login_password": login_password,
|
||||
"jwt_token": jwt_token,
|
||||
"user_id": user_id,
|
||||
"user_role": user_role
|
||||
}
|
||||
else:
|
||||
print_error(f"注册失败: {result.get('message', '未知错误')}")
|
||||
sys.exit(1)
|
||||
except requests.exceptions.RequestException as e:
|
||||
print_error(f"注册失败: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def create_profile(jwt_token):
|
||||
"""步骤2: 创建Minecraft角色"""
|
||||
print_step("步骤2: 创建Minecraft角色")
|
||||
|
||||
profile_name = f"TestPlayer{random.randint(100, 999)}"
|
||||
print_info(f"角色名: {profile_name}")
|
||||
|
||||
profile_data = {
|
||||
"name": profile_name
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {jwt_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/profile/",
|
||||
json=profile_data,
|
||||
headers=headers,
|
||||
timeout=10
|
||||
)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
if result.get("code") == 200:
|
||||
profile_uuid = result["data"]["uuid"]
|
||||
print_success("角色创建成功!")
|
||||
print_info(f"角色UUID: {profile_uuid}")
|
||||
print_info(f"角色名: {result['data']['name']}")
|
||||
|
||||
return {
|
||||
"profile_name": profile_name,
|
||||
"profile_uuid": profile_uuid
|
||||
}
|
||||
else:
|
||||
print_error(f"角色创建失败: {result.get('message', '未知错误')}")
|
||||
sys.exit(1)
|
||||
except requests.exceptions.RequestException as e:
|
||||
print_error(f"角色创建失败: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def reset_yggdrasil_password(jwt_token):
|
||||
"""步骤3: 重置并获取Yggdrasil密码"""
|
||||
print_step("步骤3: 重置并获取Yggdrasil密码")
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {jwt_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/user/yggdrasil-password/reset",
|
||||
headers=headers,
|
||||
timeout=10
|
||||
)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
if result.get("code") == 200:
|
||||
yggdrasil_password = result["data"]["password"]
|
||||
print_success("Yggdrasil密码重置成功!")
|
||||
print_info(f"Yggdrasil密码: {yggdrasil_password}")
|
||||
return yggdrasil_password
|
||||
else:
|
||||
print_error(f"Yggdrasil密码重置失败: {result.get('message', '未知错误')}")
|
||||
return ""
|
||||
except requests.exceptions.RequestException as e:
|
||||
print_error(f"Yggdrasil密码重置失败: {str(e)}")
|
||||
return ""
|
||||
|
||||
|
||||
def generate_output(user_info, profile_info, yggdrasil_password):
|
||||
"""生成输出信息"""
|
||||
output = f"""========================================
|
||||
CarrotSkin 测试账户信息
|
||||
========================================
|
||||
|
||||
=== 账户信息 ===
|
||||
用户名: {user_info['username']}
|
||||
邮箱: {user_info['email']}
|
||||
登录密码: {user_info['login_password']}
|
||||
用户ID: {user_info['user_id']}
|
||||
|
||||
=== JWT Token (API认证) ===
|
||||
Token: {user_info['jwt_token']}
|
||||
|
||||
=== 角色信息 ===
|
||||
角色名: {profile_info['profile_name']}
|
||||
角色UUID: {profile_info['profile_uuid']}
|
||||
|
||||
=== Yggdrasil信息 ===
|
||||
Yggdrasil密码: {yggdrasil_password}
|
||||
|
||||
=== 测试命令 ===
|
||||
|
||||
# 1. API登录
|
||||
curl -X POST http://localhost:8080/api/v1/auth/login \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
-d '{{"username":"{user_info['username']}","password":"{user_info['login_password']}"}}'
|
||||
|
||||
# 2. 创建角色
|
||||
curl -X POST http://localhost:8080/api/v1/profile/ \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
-H 'Authorization: Bearer {user_info['jwt_token']}' \\
|
||||
-d '{{"name":"NewProfile"}}'
|
||||
|
||||
# 3. 重置Yggdrasil密码
|
||||
curl -X POST http://localhost:8080/api/v1/user/yggdrasil-password/reset \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
-H 'Authorization: Bearer {user_info['jwt_token']}'
|
||||
|
||||
# 4. Yggdrasil认证
|
||||
curl -X POST http://localhost:8080/api/v1/yggdrasil/authserver/authenticate \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
-d '{{
|
||||
"username": "{user_info['username']}",
|
||||
"password": "{yggdrasil_password}",
|
||||
"requestUser": true
|
||||
}}'
|
||||
|
||||
# 5. 获取角色信息
|
||||
curl -X GET http://localhost:8080/api/v1/profile/{profile_info['profile_uuid']}
|
||||
|
||||
========================================
|
||||
"""
|
||||
return output
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print_header("CarrotSkin 测试账户生成器")
|
||||
|
||||
# 步骤1: 注册用户
|
||||
user_info = register_user()
|
||||
|
||||
# 步骤2: 创建角色
|
||||
profile_info = create_profile(user_info["jwt_token"])
|
||||
|
||||
# 步骤3: 重置Yggdrasil密码
|
||||
yggdrasil_password = reset_yggdrasil_password(user_info["jwt_token"])
|
||||
|
||||
# 步骤4: 输出所有信息
|
||||
print_header("测试账户信息汇总")
|
||||
|
||||
output = generate_output(user_info, profile_info, yggdrasil_password)
|
||||
print(output)
|
||||
|
||||
# 保存到文件
|
||||
output_file = f"test_account_{user_info['username']}.txt"
|
||||
try:
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
f.write(output)
|
||||
print_success(f"信息已保存到文件: {output_file}")
|
||||
except Exception as e:
|
||||
print_error(f"保存文件失败: {str(e)}")
|
||||
|
||||
print_header("测试完成!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
4
start.sh
4
start.sh
@@ -5,9 +5,9 @@
|
||||
# 设置环境变量
|
||||
export DATABASE_HOST=192.168.10.205
|
||||
export DATABASE_PORT=5432
|
||||
export DATABASE_USERNAME=skin
|
||||
export DATABASE_USERNAME=skin3
|
||||
export DATABASE_PASSWORD=lanyimin123
|
||||
export DATABASE_NAME=skin
|
||||
export DATABASE_NAME=skin3
|
||||
export DATABASE_SSL_MODE=disable
|
||||
export DATABASE_TIMEZONE=Asia/Shanghai
|
||||
|
||||
|
||||
Reference in New Issue
Block a user