新增用户

This commit is contained in:
guosl 2024-07-04 17:16:49 +08:00
parent 0d8f69e08f
commit 038d831e33
10 changed files with 244 additions and 50 deletions

2
go.mod
View File

@ -17,6 +17,7 @@ require (
github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_golang v1.19.1
github.com/redis/go-redis/v9 v9.5.3 github.com/redis/go-redis/v9 v9.5.3
github.com/samber/do v1.6.0 github.com/samber/do v1.6.0
github.com/spf13/cast v1.6.0
github.com/spf13/viper v1.19.0 github.com/spf13/viper v1.19.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
@ -58,7 +59,6 @@ require (
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect

View File

@ -4,3 +4,25 @@ type LoginInfo struct {
Account string Account string
Pwd string Pwd string
} }
type AddInfo struct {
Account string `json:"account" binding:"required"`
Name string `json:"name" binding:"required"`
Mobile string `json:"mobile" binding:"required"`
Email string `json:"email" binding:"required"`
Sex int `json:"sex"`
Pwd string `json:"pwd" binding:"required"`
}
type UserInfo struct {
Account string
Name string
Mobile string
Email string
Sex int
}
type User struct {
Id uint
UserInfo
}

View File

@ -2,6 +2,7 @@ package repo
import ( import (
"context" "context"
"fmt"
"time" "time"
"github.com/samber/do" "github.com/samber/do"
@ -34,6 +35,10 @@ func (m *User) TableName() string {
type UserRepo interface { type UserRepo interface {
GetUserByAccount(ctx context.Context, account string) (user User, err error) GetUserByAccount(ctx context.Context, account string) (user User, err error)
GetUserByMobile(ctx context.Context, mobile string) (user User, err error)
GetUserByEmail(ctx context.Context, mobile string) (user User, err error)
SaveUser(ctx context.Context, user User) error
CreateUser(ctx context.Context, user *User) error
} }
type userRepoS struct { type userRepoS struct {
@ -50,3 +55,25 @@ func (u *userRepoS) GetUserByAccount(ctx context.Context, account string) (user
err = u.db.Where("account = ?", account).Take(&user).Error err = u.db.Where("account = ?", account).Take(&user).Error
return return
} }
func (u *userRepoS) GetUserByMobile(ctx context.Context, mobile string) (user User, err error) {
err = u.db.Where("mobile = ?", mobile).Take(&user).Error
return
}
func (u *userRepoS) SaveUser(ctx context.Context, user User) error {
if user.ID <= 0 {
return fmt.Errorf("错误用户id:%d", user.ID)
}
return u.db.Save(user).Error
}
func (u *userRepoS) CreateUser(ctx context.Context, user *User) error {
return u.db.Create(user).Error
}
func (u *userRepoS) GetUserByEmail(ctx context.Context, email string) (user User, err error) {
err = u.db.Where("email = ?", email).Take(&user).Error
return
}

View File

@ -6,6 +6,6 @@ import (
) )
type UserService interface { type UserService interface {
Add() error Add(ctx context.Context, info *models.AddInfo) (id uint, err error)
Login(ctx context.Context, lInfo models.LoginInfo) error Login(ctx context.Context, lInfo models.LoginInfo) error
} }

View File

@ -5,14 +5,12 @@ import (
"busniess-user-center/internal/models" "busniess-user-center/internal/models"
"busniess-user-center/internal/repo" "busniess-user-center/internal/repo"
"busniess-user-center/pkg/redis" "busniess-user-center/pkg/redis"
contextUtil "busniess-user-center/pkg/utils/context"
stringUtil "busniess-user-center/pkg/utils/string"
"busniess-user-center/pkg/utils/token" "busniess-user-center/pkg/utils/token"
"context" "context"
"fmt" "fmt"
"github.com/gin-gonic/gin"
"github.com/golang-module/dongle"
"github.com/dgrijalva/jwt-go"
"github.com/samber/do" "github.com/samber/do"
"go.uber.org/zap" "go.uber.org/zap"
"gorm.io/gorm" "gorm.io/gorm"
@ -22,6 +20,7 @@ const (
COOKIE_KEY_TOKEN = "token" COOKIE_KEY_TOKEN = "token"
COOKIE_KEY_ACCOUNT = "account" COOKIE_KEY_ACCOUNT = "account"
COOKIE_KEY_ID = "id" COOKIE_KEY_ID = "id"
saltLen = 8
) )
func init() { func init() {
@ -46,8 +45,59 @@ func NewUserService(i *do.Injector) (UserService, error) {
}, nil }, nil
} }
func (u *userService) Add() error { func (u *userService) Add(ctx context.Context, info *models.AddInfo) (id uint, err error) {
return nil session, err := contextUtil.GetSession(ctx)
if err != nil {
return
}
// 判断手机号
_, err = u.repo.GetUserByAccount(ctx, info.Account)
if err != nil && err != gorm.ErrRecordNotFound {
return
}
if err == nil {
err = fmt.Errorf("用户%s已经存在", info.Account)
return
}
// 判断账号,手机号是否唯一
mUser, err := u.repo.GetUserByMobile(ctx, info.Mobile)
if err != nil && err != gorm.ErrRecordNotFound {
return
}
if err == nil {
err = fmt.Errorf("手机号:%s的用户%s已经存在", info.Mobile, mUser.Account)
return
}
// 邮箱
eUser, err := u.repo.GetUserByEmail(ctx, info.Email)
if err != nil && err != gorm.ErrRecordNotFound {
return
}
if err == nil {
err = fmt.Errorf("邮箱:%s的用户%s已经存在", info.Mobile, eUser.Account)
}
salt := stringUtil.RandStringRunes(saltLen)
pwd := u.sha256(info.Pwd, salt)
user := repo.User{
Account: info.Account,
Name: info.Name,
Mobile: info.Mobile,
Email: info.Email,
Pwd: pwd,
Sex: info.Sex,
CreatedBy: session.Account,
}
err = u.repo.CreateUser(ctx, &user)
id = user.ID
return
} }
func (u *userService) Login(ctx context.Context, info models.LoginInfo) error { func (u *userService) Login(ctx context.Context, info models.LoginInfo) error {
@ -72,35 +122,45 @@ func (u *userService) Login(ctx context.Context, info models.LoginInfo) error {
return u.setLoginStatus(ctx, user, claims) return u.setLoginStatus(ctx, user, claims)
} }
func (u *userService) sha256(pwd string, salt string) string { func (u *userService) LoginOut() error {
fromStr := fmt.Sprintf("%s:%s", pwd, salt) // 获取当前用户信息
return dongle.Encrypt.FromString(fromStr).BySha256().ToHexString() // 删除redis缓存
} // remome cookie
return nil
func (u *userService) getToken(claims jwt.MapClaims) (string, error) { }
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenStr, err := token.SignedString([]byte(u.conf.Jwt.Secret)) func (u *userService) Modify() error {
return tokenStr, err // 获取当前操作用户
} // 判断修改用户是否是同一个人
return nil
func (u *userService) setLoginStatus(ctx context.Context, user repo.User, claims jwt.MapClaims) error { }
tokenStr, err := u.getToken(claims)
if err != nil { func (u *userService) Disable() error {
return err // 获取操作用户
} // 判断是否有权限
// 修改对应用户状态
// 只做简单的token记录,校验时如果没有从reids获取到登陆信息,返回失败 return nil
err = u.tokenRefresher.SetUseridTokenRelation(user.ID, tokenStr) }
if err != nil {
return fmt.Errorf("设置redis失败:%s", err.Error()) func (u *userService) Able() error {
} // 获取操作用户
// 判断是否有权限
c := ctx.(*gin.Context) // 修改对应用户状态
expires := u.conf.Jwt.Expires return nil
domain := u.conf.App.Host }
c.Writer.Header().Add("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%d; Path=/;Domain=%s", COOKIE_KEY_TOKEN, tokenStr, expires, domain))
c.Writer.Header().Add("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%d; Path=/;Domain=%s", COOKIE_KEY_ACCOUNT, claims["account"], expires, domain)) func (u *userService) Users() error {
c.Writer.Header().Add("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%d; Path=/;Domain=%s", COOKIE_KEY_ID, claims["id"], expires, domain)) // 获取操作用户
// 判断是否有权限
// 返回用户列表
return nil
}
func (u *userService) ResetPwd() error {
// 获取操作用户
// 判断是否本人操作
// 判断当前密码是否正确
// 生成新的密码并保存
// 删除旧的缓存
return nil return nil
} }

View File

@ -2,9 +2,13 @@ package user
import ( import (
"busniess-user-center/internal/repo" "busniess-user-center/internal/repo"
"context"
"fmt"
"time" "time"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/golang-module/dongle"
) )
func creteLoginTokenClaims(user *repo.User, expire int) jwt.MapClaims { func creteLoginTokenClaims(user *repo.User, expire int) jwt.MapClaims {
@ -19,3 +23,36 @@ func creteLoginTokenClaims(user *repo.User, expire int) jwt.MapClaims {
return userTokenClaims return userTokenClaims
} }
func (u *userService) sha256(pwd string, salt string) string {
fromStr := fmt.Sprintf("%s:%s", pwd, salt)
return dongle.Encrypt.FromString(fromStr).BySha256().ToHexString()
}
func (u *userService) getToken(claims jwt.MapClaims) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenStr, err := token.SignedString([]byte(u.conf.Jwt.Secret))
return tokenStr, err
}
func (u *userService) setLoginStatus(ctx context.Context, user repo.User, claims jwt.MapClaims) error {
tokenStr, err := u.getToken(claims)
if err != nil {
return err
}
// 只做简单的token记录,校验时如果没有从reids获取到登陆信息,返回失败
err = u.tokenRefresher.SetUseridTokenRelation(user.ID, tokenStr)
if err != nil {
return fmt.Errorf("设置redis失败:%s", err.Error())
}
c := ctx.(*gin.Context)
expires := u.conf.Jwt.Expires
domain := u.conf.App.Host
c.Writer.Header().Add("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%d; Path=/;Domain=%s", COOKIE_KEY_TOKEN, tokenStr, expires, domain))
c.Writer.Header().Add("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%d; Path=/;Domain=%s", COOKIE_KEY_ACCOUNT, claims["account"], expires, domain))
c.Writer.Header().Add("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%d; Path=/;Domain=%s", COOKIE_KEY_ID, claims["id"], expires, domain))
return nil
}

View File

@ -11,7 +11,7 @@ import (
jwt "github.com/appleboy/gin-jwt/v2" jwt "github.com/appleboy/gin-jwt/v2"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/spf13/cast"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -75,7 +75,7 @@ func NewJwtAuthMiddleware(logger *zap.SugaredLogger, refresher *token.TokenRefre
idInterface, ok := claims[JwtIdentityKey] idInterface, ok := claims[JwtIdentityKey]
if ok { if ok {
id = idInterface.(uint) id = cast.ToUint(idInterface)
} }
// 从redis获取 // 从redis获取
@ -108,6 +108,7 @@ func NewJwtAuthMiddleware(logger *zap.SugaredLogger, refresher *token.TokenRefre
}, },
Authorizator: func(data interface{}, c *gin.Context) bool { Authorizator: func(data interface{}, c *gin.Context) bool {
if v, ok := data.(*session.Session); ok { if v, ok := data.(*session.Session); ok {
_ = contextUtil.PutSession(c, v)
c.Request = c.Request.WithContext(contextUtil.PutSession(c.Request.Context(), v)) c.Request = c.Request.WithContext(contextUtil.PutSession(c.Request.Context(), v))
return v.ID != 0 return v.ID != 0
} }

View File

@ -6,6 +6,12 @@ import (
pkgErrors "busniess-user-center/pkg/errors" pkgErrors "busniess-user-center/pkg/errors"
"busniess-user-center/pkg/utils/session" "busniess-user-center/pkg/utils/session"
"github.com/gin-gonic/gin"
)
const (
JwtIdentityKey = "id"
) )
type contextSessionKey struct{} type contextSessionKey struct{}
@ -32,18 +38,34 @@ func GetSessionUserID(ctx context.Context) (userID uint, err error) {
if err != nil { if err != nil {
return 0, errors.New(pkgErrors.UNAUTH) return 0, errors.New(pkgErrors.UNAUTH)
} }
return sess.ID, nil return sess.ID, nil
} }
func GetSession(ctx context.Context) (sess *session.Session, err error) { func GetSession(ctx context.Context) (sess *session.Session, err error) {
sess, ok := ctx.Value(contextSessionKey{}).(*session.Session) switch c := ctx.(type) {
if ok { case *gin.Context:
return if sessionI, ok := c.Get(JwtIdentityKey); ok {
if sess, ok = sessionI.(*session.Session); ok {
return sess, nil
}
}
default:
if sess, ok := ctx.Value(contextSessionKey{}).(*session.Session); ok {
return sess, nil
}
} }
return nil, errors.New(pkgErrors.UNAUTH) return nil, errors.New(pkgErrors.UNAUTH)
} }
func PutSession(ctx context.Context, sess *session.Session) (newCtx context.Context) { func PutSession(ctx context.Context, sess *session.Session) context.Context {
switch c := ctx.(type) {
case *gin.Context:
c.Set(JwtIdentityKey, sess)
return ctx
default:
return context.WithValue(ctx, contextSessionKey{}, sess) return context.WithValue(ctx, contextSessionKey{}, sess)
}
} }

View File

@ -0,0 +1,22 @@
package string
import (
"math/rand"
"time"
)
var (
letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
)
func init() {
rand.Seed(time.Now().UnixNano())
}
func RandStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}

View File

@ -3,6 +3,7 @@ package user
import ( import (
"busniess-user-center/internal/models" "busniess-user-center/internal/models"
"busniess-user-center/internal/service/user" "busniess-user-center/internal/service/user"
ginUtil "busniess-user-center/pkg/utils/gin" ginUtil "busniess-user-center/pkg/utils/gin"
"busniess-user-center/server/user/proto" "busniess-user-center/server/user/proto"
"context" "context"
@ -34,20 +35,22 @@ func RegisterRoute(api *gin.RouterGroup) {
api.POST("/login", ginUtil.WrapNoRsp(server.Login)) api.POST("/login", ginUtil.WrapNoRsp(server.Login))
} }
func (u *UserServer) Add(ctx context.Context, req *proto.AddRequst) (rsp proto.AddResponse, err error) { func (u *UserServer) Add(ctx context.Context, req *models.AddInfo) (rsp proto.AddResponse, err error) {
// 转换dto // 转换dto
err = u.userService.Add() id, err := u.userService.Add(ctx, req)
if err != nil { if err != nil {
u.logger.Errorf("add user err:", err.Error()) u.logger.Errorf("add user err:", err.Error())
return return
} }
rsp.Id = 1 rsp.Id = id
rsp.Account = "test" rsp.Account = req.Account
rsp.Name = req.Name
return return
} }
func (u *UserServer) Login(ctx context.Context, req *proto.LoginRequest) (err error) { func (u *UserServer) Login(ctx context.Context, req *models.LoginInfo) (err error) {
// 转换dto // 转换dto
info := models.LoginInfo{ info := models.LoginInfo{
Account: req.Account, Account: req.Account,