完成基础功能
This commit is contained in:
2
go.mod
2
go.mod
@@ -3,6 +3,7 @@ module go-user-service
|
|||||||
go 1.24.3
|
go 1.24.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
golang.org/x/crypto v0.41.0
|
||||||
google.golang.org/grpc v1.75.0
|
google.golang.org/grpc v1.75.0
|
||||||
google.golang.org/protobuf v1.36.8
|
google.golang.org/protobuf v1.36.8
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
@@ -19,7 +20,6 @@ require (
|
|||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // 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/net v0.42.0 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.35.0 // indirect
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
|||||||
14
go.sum
14
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/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 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
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 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
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.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
|
||||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
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.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
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.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
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 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
|
|||||||
@@ -17,5 +17,5 @@ type User struct {
|
|||||||
|
|
||||||
// 实现 TableName 方法指定表名
|
// 实现 TableName 方法指定表名
|
||||||
func (User) TableName() string {
|
func (User) TableName() string {
|
||||||
return "d_user"
|
return "user"
|
||||||
}
|
}
|
||||||
|
|||||||
17
models/user_token.go
Normal file
17
models/user_token.go
Normal file
@@ -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"
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ message LoginRequest {
|
|||||||
|
|
||||||
// 登录响应
|
// 登录响应
|
||||||
message LoginResponse {
|
message LoginResponse {
|
||||||
string user_id = 1;
|
int64 user_id = 1;
|
||||||
string username = 2;
|
string username = 2;
|
||||||
string email = 3;
|
string email = 3;
|
||||||
string access_token = 4;
|
string access_token = 4;
|
||||||
@@ -23,11 +23,13 @@ message RegisterRequest {
|
|||||||
string username = 1;
|
string username = 1;
|
||||||
string password = 2;
|
string password = 2;
|
||||||
string email = 3;
|
string email = 3;
|
||||||
|
string nickname = 4;
|
||||||
|
string phone = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户注册响应
|
// 用户注册响应
|
||||||
message RegisterResponse {
|
message RegisterResponse {
|
||||||
string user_id = 1;
|
int64 user_id = 1;
|
||||||
string username = 2;
|
string username = 2;
|
||||||
string email = 3;
|
string email = 3;
|
||||||
}
|
}
|
||||||
@@ -39,7 +41,7 @@ message ValidateTokenRequest {
|
|||||||
|
|
||||||
// 令牌验证响应
|
// 令牌验证响应
|
||||||
message ValidateTokenResponse {
|
message ValidateTokenResponse {
|
||||||
string user_id = 1;
|
int64 user_id = 1;
|
||||||
bool is_valid = 2;
|
bool is_valid = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"go-user-service/config"
|
||||||
"go-user-service/libs"
|
"go-user-service/libs"
|
||||||
"go-user-service/models"
|
"go-user-service/models"
|
||||||
userpb "go-user-service/proto/gen"
|
userpb "go-user-service/proto/gen"
|
||||||
"go-user-service/utils"
|
"go-user-service/utils"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
@@ -14,11 +16,13 @@ import (
|
|||||||
|
|
||||||
type UserService struct {
|
type UserService struct {
|
||||||
userpb.UnimplementedUserServiceServer
|
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) {
|
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()
|
u.mu.RLock()
|
||||||
defer u.mu.RUnlock()
|
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)
|
hashPassword, _ := crypto.PasswordEncryption(user.Password + user.Salt)
|
||||||
|
|
||||||
if user.Password == hashPassword {
|
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.Unauthenticated, "密码错误")
|
||||||
}
|
}
|
||||||
return nil, status.Errorf(codes.NotFound, "用户不存在")
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
randa "math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
@@ -54,3 +55,18 @@ func (*Crypto) GenerateStateToken(tokenLength int) (string, error) {
|
|||||||
token := base64.URLEncoding.EncodeToString(hash[:])
|
token := base64.URLEncoding.EncodeToString(hash[:])
|
||||||
return token, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user