From b67fef0683b81c807cceb9d899401316b4bdba75 Mon Sep 17 00:00:00 2001 From: zyj <18107291228@163.com> Date: Tue, 2 Sep 2025 11:39:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=9F=BA=E7=A1=80=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- go.sum | 14 ++----- models/user.go | 2 +- models/user_token.go | 17 +++++++++ proto/user.proto | 8 ++-- service/user_service.go | 85 +++++++++++++++++++++++++++++++++++++++-- utils/crypto.go | 16 ++++++++ 7 files changed, 126 insertions(+), 18 deletions(-) create mode 100644 models/user_token.go diff --git a/go.mod b/go.mod index 0b069b7..14fc2da 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module go-user-service go 1.24.3 require ( + golang.org/x/crypto v0.41.0 google.golang.org/grpc v1.75.0 google.golang.org/protobuf v1.36.8 gopkg.in/yaml.v3 v3.0.1 @@ -19,7 +20,6 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/kr/text v0.2.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect - golang.org/x/crypto v0.41.0 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.35.0 // indirect diff --git a/go.sum b/go.sum index c0e7fc1..07f1e2f 100644 --- a/go.sum +++ b/go.sum @@ -49,21 +49,15 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= diff --git a/models/user.go b/models/user.go index 8b1d221..78f9019 100644 --- a/models/user.go +++ b/models/user.go @@ -17,5 +17,5 @@ type User struct { // 实现 TableName 方法指定表名 func (User) TableName() string { - return "d_user" + return "user" } diff --git a/models/user_token.go b/models/user_token.go new file mode 100644 index 0000000..ef32b77 --- /dev/null +++ b/models/user_token.go @@ -0,0 +1,17 @@ +package models + +import "time" + +type UserToken struct { + ID int8 `gorm:"id;primary_key"` + UserId int8 `gorm:"UserId"` + Token string `gorm:"token"` + ExpirationTime time.Time `gorm:"expiration_time"` + CreatedAt time.Time `gorm:"created_at;type:timestamptz"` + UpdatedAt time.Time `gorm:"updated_at;type:timestamptz"` +} + +// 实现 TableName 方法指定表名 +func (UserToken) TableName() string { + return "user_token" +} diff --git a/proto/user.proto b/proto/user.proto index 8c5151a..c9af424 100644 --- a/proto/user.proto +++ b/proto/user.proto @@ -11,7 +11,7 @@ message LoginRequest { // 登录响应 message LoginResponse { - string user_id = 1; + int64 user_id = 1; string username = 2; string email = 3; string access_token = 4; @@ -23,11 +23,13 @@ message RegisterRequest { string username = 1; string password = 2; string email = 3; + string nickname = 4; + string phone = 5; } // 用户注册响应 message RegisterResponse { - string user_id = 1; + int64 user_id = 1; string username = 2; string email = 3; } @@ -39,7 +41,7 @@ message ValidateTokenRequest { // 令牌验证响应 message ValidateTokenResponse { - string user_id = 1; + int64 user_id = 1; bool is_valid = 2; } diff --git a/service/user_service.go b/service/user_service.go index 1aac059..2de9b1f 100644 --- a/service/user_service.go +++ b/service/user_service.go @@ -2,11 +2,13 @@ package service import ( "context" + "go-user-service/config" "go-user-service/libs" "go-user-service/models" userpb "go-user-service/proto/gen" "go-user-service/utils" "sync" + "time" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -14,11 +16,13 @@ import ( type UserService struct { userpb.UnimplementedUserServiceServer - tokens map[string]string // token -> userID - mu sync.RWMutex + mu sync.RWMutex } +// 登录 func (u *UserService) Login(ctx context.Context, req *userpb.LoginRequest) (*userpb.LoginResponse, error) { + appConfig, _ := config.LoadConfig() + Snowflake, _ := utils.NewSnowflake(appConfig.WorkerID, appConfig.DatacenterID) u.mu.RLock() defer u.mu.RUnlock() // 查询是否有这个用户名 @@ -31,9 +35,84 @@ func (u *UserService) Login(ctx context.Context, req *userpb.LoginRequest) (*use hashPassword, _ := crypto.PasswordEncryption(user.Password + user.Salt) if user.Password == hashPassword { - + token, _ := crypto.GenerateStateToken(32) + userId := Snowflake.NextID() + db.Create(&models.UserToken{ + ID: int8(userId), + UserId: user.ID, + Token: token, + ExpirationTime: time.Now().Add(24 * time.Hour), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + return &userpb.LoginResponse{ + UserId: userId, + Username: user.Username, + Email: user.Email, + AccessToken: token, + ExpiresIn: 86400, // 1小时 + }, nil } return nil, status.Errorf(codes.Unauthenticated, "密码错误") } return nil, status.Errorf(codes.NotFound, "用户不存在") } + +// 注册 +func (u *UserService) Register(ctx context.Context, req *userpb.RegisterRequest) (*userpb.RegisterResponse, error) { + appConfig, _ := config.LoadConfig() + Snowflake, _ := utils.NewSnowflake(appConfig.WorkerID, appConfig.DatacenterID) + var crypto utils.Crypto + u.mu.RLock() + defer u.mu.RUnlock() + // 查询是否有这个用户名 + var user models.User + db := libs.GetDB() + db.Model(&models.User{}).Where("username = ?", req.Username).First(&user) + if user.Username == req.Username { + return nil, status.Errorf(codes.AlreadyExists, "用户名已存在") + } + db.Model(&models.User{}).Where("email = ?", req.Email).First(&user) + if user.Email == req.Email { + return nil, status.Errorf(codes.AlreadyExists, "邮箱已存在") + } + userId := Snowflake.NextID() + salt := crypto.GetRandString(10) + hashPassword, _ := crypto.PasswordEncryption(req.Password + salt) + db.Create(&models.User{ + ID: int8(userId), + Nickname: req.Nickname, + Username: req.Username, + Password: hashPassword, + Status: 1, + Salt: salt, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) + return &userpb.RegisterResponse{ + UserId: userId, + Username: req.Username, + Email: req.Email, + }, nil +} + +// 验证token信息 +func (u *UserService) ValidateToken(ctx context.Context, req *userpb.ValidateTokenRequest) (*userpb.ValidateTokenResponse, error) { + u.mu.RLock() + defer u.mu.RUnlock() + // 查询是否有这个用户名 + var userToken models.UserToken + var user models.User + db := libs.GetDB() + db.Model(&models.UserToken{}).Where("token = ? and expiration_time>?", req.AccessToken, time.Now()).First(&userToken) + if userToken.Token != req.AccessToken { + return &userpb.ValidateTokenResponse{ + IsValid: false, + }, nil + } + db.Model(&models.User{}).Where("id = ?", userToken.UserId).First(&user) + return &userpb.ValidateTokenResponse{ + UserId: int64(userToken.UserId), + IsValid: true, + }, nil +} diff --git a/utils/crypto.go b/utils/crypto.go index 7fd84dc..e8df819 100644 --- a/utils/crypto.go +++ b/utils/crypto.go @@ -6,6 +6,7 @@ import ( "encoding/base64" "fmt" "log" + randa "math/rand" "time" "golang.org/x/crypto/bcrypt" @@ -54,3 +55,18 @@ func (*Crypto) GenerateStateToken(tokenLength int) (string, error) { token := base64.URLEncoding.EncodeToString(hash[:]) return token, nil } + +func (*Crypto) GetRandString(length int) string { + // 初始化随机数种子 + randa.Seed(time.Now().UnixNano()) + + const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + + // 方法1: 使用字节切片 + b := make([]byte, length) + for i := range b { + b[i] = letters[randa.Intn(len(letters))] // 随机选取一个字符 + } + randomString := string(b) + return randomString +}