完成基础功能

This commit is contained in:
zyj
2025-09-02 11:39:06 +08:00
parent a94f6b5a25
commit b67fef0683
7 changed files with 126 additions and 18 deletions

2
go.mod
View File

@@ -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
View File

@@ -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=

View File

@@ -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
View 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"
}

View File

@@ -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;
} }

View File

@@ -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
}

View File

@@ -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
}