From 159ef59749bf0225f74677cb7a860228aaf907c2 Mon Sep 17 00:00:00 2001 From: Echo <1711788888@qq.com> Date: Fri, 9 May 2025 11:16:38 +0800 Subject: [PATCH] =?UTF-8?q?:art:=20=E5=AE=8C=E5=96=84=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/v1/user/user.go | 128 ++++++++++++++++++++++++++++++++++ initialize/router_biz.go | 1 + model/user/request/user.go | 61 ++++++++++------- model/user/user.go | 23 ++++--- router/user/user.go | 24 ++++--- service/user/user.go | 136 +++++++++++++++++++++++++++++++++++-- 6 files changed, 328 insertions(+), 45 deletions(-) diff --git a/api/v1/user/user.go b/api/v1/user/user.go index 9757d13..12c38f0 100644 --- a/api/v1/user/user.go +++ b/api/v1/user/user.go @@ -7,6 +7,7 @@ import ( "git.echol.cn/loser/lckt/model/user/request" "git.echol.cn/loser/lckt/utils" "git.echol.cn/loser/lckt/utils/user_jwt" + "git.echol.cn/loser/lckt/utils/wechat" "github.com/gin-gonic/gin" "github.com/redis/go-redis/v9" "go.uber.org/zap" @@ -92,7 +93,45 @@ func (*APPUserApi) WechatLogin(ctx *gin.Context) { global.GVA_LOG.Error("参数错误,登录失败", zap.Error(err)) return } + //Todo 待完善微信登录 + info := wechat.GetUserInfo(p.Code) + if info == nil { + r.FailWithMessage("获取用户信息失败", ctx) + return + } + + user, err := userService.WechatLogin(info) + if err != nil { + r.FailWithMessage("登录失败", ctx) + return + } + // 生成token + token, claims, err := user_jwt.LoginToken(user) + if err != nil { + global.GVA_LOG.Error("获取token失败!", zap.Error(err)) + r.FailWithMessage("获取token失败", ctx) + return + } + if _, err = global.GVA_REDIS.Get(ctx, user.Phone).Result(); err == redis.Nil { + // 此处过期时间等于jwt过期时间 + dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime) + if err != nil { + return + } + timer := dr + if err := global.GVA_REDIS.Set(ctx, user.Phone, token, timer).Err(); err != nil { + global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err)) + r.FailWithMessage("设置登录状态失败", ctx) + return + } + user_jwt.SetToken(ctx, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix())) + r.OkWithDetailed(gin.H{ + "User": user, + "Token": token, + "ExpiresAt": claims.RegisteredClaims.ExpiresAt.Unix() * 1000, + }, "登录成功", ctx) + } } // GetUserList 获取用户列表 @@ -130,3 +169,92 @@ func (*APPUserApi) SetBalance(ctx *gin.Context) { } r.OkWithMessage("设置用户余额成功", ctx) } + +// PwdLogin 密码登录 +func (*APPUserApi) PwdLogin(ctx *gin.Context) { + var p request.PwdLoginReq + if err := ctx.ShouldBind(&p); err != nil { + r.FailWithMessage(err.Error(), ctx) + global.GVA_LOG.Error("参数错误,登录失败", zap.Error(err)) + return + } + + user, err := userService.PwdLogin(p) + if err != nil { + r.FailWithMessage("手机号或密码错误!", ctx) + return + } + + // 生成token + token, claims, err := user_jwt.LoginToken(user) + if err != nil { + global.GVA_LOG.Error("获取token失败!", zap.Error(err)) + r.FailWithMessage("获取token失败", ctx) + return + } + + // 此处过期时间等于jwt过期时间 + dr, err := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime) + if err != nil { + return + } + timer := dr + if err := global.GVA_REDIS.Set(ctx, user.Phone, token, timer).Err(); err != nil { + global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err)) + r.FailWithMessage("设置登录状态失败", ctx) + return + } + user_jwt.SetToken(ctx, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix())) + r.OkWithDetailed(gin.H{ + "User": user, + "Token": token, + "ExpiresAt": claims.RegisteredClaims.ExpiresAt.Unix() * 1000, + }, "登录成功", ctx) +} + +// Register 注册-后台用 +func (*APPUserApi) Register(ctx *gin.Context) { + var p request.RegisterReq + if err := ctx.ShouldBind(&p); err != nil { + r.FailWithMessage(err.Error(), ctx) + global.GVA_LOG.Error("参数错误,注册失败", zap.Error(err)) + return + } + + if err := userService.Register(p); err != nil { + r.FailWithMessage("注册失败", ctx) + return + } + + r.OkWithMessage("注册成功", ctx) +} + +// SetUserStatus 设置用户状态 +func (*APPUserApi) SetUserStatus(ctx *gin.Context) { + id := ctx.Param("id") + if id == "" { + r.FailWithMessage("参数错误", ctx) + return + } + + if err := userService.SetUserStatus(id); err != nil { + r.FailWithMessage("设置用户状态失败", ctx) + return + } + r.OkWithMessage("设置用户状态成功", ctx) +} + +// GetUserById 根据id获取用户信息 +func (*APPUserApi) GetUserById(ctx *gin.Context) { + id := ctx.Param("id") + if id == "" { + r.FailWithMessage("参数错误", ctx) + return + } + user, err := userService.GetUserById(id) + if err != nil { + r.FailWithMessage("获取用户信息失败", ctx) + return + } + r.OkWithDetailed(user, "获取用户信息成功", ctx) +} diff --git a/initialize/router_biz.go b/initialize/router_biz.go index 3e6fa1e..d9141ad 100644 --- a/initialize/router_biz.go +++ b/initialize/router_biz.go @@ -28,5 +28,6 @@ func initBizRouter(routers ...*gin.RouterGroup) { { userRouter := router.RouterGroupApp.User userRouter.InitUserRouter(privateGroup, publicGroup) + userRouter.InitAppUserRouter(publicGroup) } } diff --git a/model/user/request/user.go b/model/user/request/user.go index 4eed1cd..b319609 100644 --- a/model/user/request/user.go +++ b/model/user/request/user.go @@ -3,49 +3,64 @@ package request import "git.echol.cn/loser/lckt/model/common/request" type SendCodeReq struct { - Phone string `json:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"` + Phone string `json:"phone" form:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"` } type CodeLoginReq struct { - Phone string `json:"phone" vd:"@:len($)>0; msg:'请输入手机号码'"` - Code string `json:"code" vd:"@:len($)>0; msg:'请输入短信验证码'"` + Phone string `json:"phone" form:"phone" vd:"@:len($)>0; msg:'请输入手机号码'"` + Code string `json:"code" form:"code" vd:"@:len($)>0; msg:'请输入短信验证码'"` } type InviteLoginReq struct { - Phone string `json:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"` - Code string `json:"code" vd:"@:len($)>0; msg:'验证码不能为空'"` - Type string `json:"type" vd:"@:len($)>0; msg:'邀请类型不能为空'"` - Token string `json:"token" vd:"@:len($)>0; msg:'token不能为空'"` + Phone string `json:"phone" form:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"` + Code string `json:"code" form:"code" vd:"@:len($)>0; msg:'验证码不能为空'"` + Type string `json:"type" form:"type" vd:"@:len($)>0; msg:'邀请类型不能为空'"` + Token string `json:"token" form:"token" vd:"@:len($)>0; msg:'token不能为空'"` } type WxLoginReq struct { - OpenId string `json:"openId" binding:"required"` - NickName string `json:"nickName"` - Gender int `json:"gender"` - UnionId string `json:"unionId" binding:"required"` - AvatarUrl string `json:"avatarUrl"` - Province string `json:"province"` - City string `json:"city"` + OpenId string `json:"openId" form:"openId" binding:"required"` + NickName string `json:"nickName" form:"nickName"` + Gender int `json:"gender" form:"gender"` + UnionId string `json:"unionId" form:"unionId" binding:"required"` + AvatarUrl string `json:"avatarUrl" form:"avatarUrl"` + Province string `json:"province" form:"province"` + City string `json:"city" form:"city"` } type BindWechatReq struct { - Id int `json:"id" vd:"@:len($)>0; msg:'id不能为空'"` - Openid string `json:"openid" vd:"@:len($)>0; msg:'OpenId不能为空'"` - UnionId string `json:"unionId" gorm:"type:varchar(64);comment:用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的 unionid 是唯一的。"` + Id int `json:"id" form:"id" vd:"@:len($)>0; msg:'id不能为空'"` + Openid string `json:"openid" form:"openid" vd:"@:len($)>0; msg:'OpenId不能为空'"` + UnionId string `json:"unionId" form:"unionId" gorm:"type:varchar(64);comment:用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的 unionid 是唯一的。"` } type BindPhoneReq struct { - Id int `json:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"` - Phone string `json:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"` + Id int `json:"id" form:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"` + Phone string `json:"phone" form:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"` } type GetUserListReq struct { request.PageInfo - Type int `json:"type"` - UserLabel string `json:"user_label" ` + Type int `json:"type" form:"type"` + UserLabel string `json:"user_label" form:"user_label" ` + Status int `json:"status" form:"status"` } type SetBalanceReq struct { - Id int `json:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"` - Balance float32 `json:"balance" vd:"@:len($)>0; msg:'余额不能为空'"` + Id int `json:"id" form:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"` + Balance float32 `json:"balance" form:"balance" vd:"@:len($)>0; msg:'余额不能为空'"` +} + +type PwdLoginReq struct { + Phone string `json:"phone" form:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"` + Password string `json:"password" form:"password" vd:"@:len($)>0; msg:'密码不能为空'"` + NickName string `json:"nick_name" form:"nick_name"` +} + +type RegisterReq struct { + Phone string `json:"phone" form:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"` + Password string `json:"password" form:"password" vd:"@:len($)>0; msg:'密码不能为空'"` + NickName string `json:"nick_name" form:"nick_name"` + UserType int8 `json:"user_type" form:"user_type" vd:"@:len($)>0; msg:'用户类型不能为空'"` + UserLabel int64 `json:"user_label" form:"user_label" vd:"@:len($)>0; msg:'用户标签不能为空'"` } diff --git a/model/user/user.go b/model/user/user.go index 5c58afc..7b6c716 100644 --- a/model/user/user.go +++ b/model/user/user.go @@ -4,18 +4,21 @@ import "git.echol.cn/loser/lckt/global" type User struct { global.GVA_MODEL - NickName string `json:"nick_name" gorm:"comment:用户昵称"` - Phone string `json:"phone" gorm:"comment:用户手机号"` - UnionId string `json:"union_id" gorm:"type:varchar(255) comment '微信UnionId'"` + NickName string `json:"nick_name" form:"nick_name" gorm:"comment:用户昵称"` + Phone string `json:"phone" form:"phone" gorm:"comment:用户手机号"` + UnionId string `json:"union_id" form:"union_id" gorm:"type:varchar(255) comment '微信UnionId'"` OpenId string `gorm:"column:open_id;default:'';comment:'openId'" json:"-"` - Avatar string `json:"avatar" gorm:"comment:用户头像"` - Status int8 `gorm:"column:status;default:1;NOT NULL;comment:'用户状态 0 封禁 1 正常'" json:"status"` + Password string `json:"password" form:"password" gorm:"comment:用户密码"` + Avatar string `json:"avatar" form:"avatar" gorm:"comment:用户头像"` + Status int8 `gorm:"column:status;default:1;NOT NULL;comment:'用户状态 0 封禁 1 正常'" json:"status" form:"status"` //邀请码 - InviteCode *string `json:"invite_code" gorm:"type:varchar(255) comment '用户专属邀请码'"` - Balance float32 `json:"balance" gorm:"type:decimal(10,2);comment:学员余额"` - CommenderId int `json:"commender_id" gorm:"comment:推荐人ID"` - UserLabel string `json:"user_label" gorm:"type:varchar(255) comment '用户标签 普通/VIP/SVIP'"` - UserType int8 `gorm:"column:user_type;default:1;NOT NULL;comment:'用户类型 1 用户 2 讲师'" json:"user_type"` + InviteCode *string `json:"invite_code" form:"invite_code" gorm:"type:varchar(255) comment '用户专属邀请码'"` + Balance float32 `json:"balance" form:"balance" gorm:"type:decimal(10,2);comment:学员余额"` + CommenderId int `json:"commender_id" form:"commender_id" gorm:"comment:推荐人ID"` + UserLabel int64 `json:"user_label" form:"user_label" gorm:"comment:用户标签 1 普通用户 2 Vip 3 Svip"` + UserType int8 `gorm:"column:user_type;default:1;NOT NULL;comment:'用户类型 1 用户 2 讲师'" json:"user_type" form:"user_type" ` + IsVip int8 `gorm:"column:is_vip;default:0;NOT NULL;comment:'是否是VIP 0 否 1 是'" json:"is_vip" form:"is_vip"` + VipExpireTime string `json:"vip_expire_time" form:"vip_expire_time" gorm:"comment:VIP过期时间"` } func (User) TableName() string { diff --git a/router/user/user.go b/router/user/user.go index af860d6..004700a 100644 --- a/router/user/user.go +++ b/router/user/user.go @@ -10,14 +10,22 @@ type UserRouter struct{} // InitUserRouter 初始化 用户相关 路由信息 func (s *UserRouter) InitUserRouter(Router *gin.RouterGroup, PublicRouter *gin.RouterGroup) { userRouter := Router.Group("app_user").Use(middleware.OperationRecord()) - userRouterWithoutAuth := PublicRouter.Group("app_user") { - userRouter.GET("list", userApi.GetUserList) // 获取用户列表 - userRouter.PUT("setBalance", userApi.SetBalance) // 更新用户余额 + userRouter.GET("list", userApi.GetUserList) // 获取用户列表 + userRouter.PUT("setBalance", userApi.SetBalance) // 更新用户余额 + userRouter.POST("register", userApi.Register) // 注册 + userRouter.PUT("status/:id", userApi.SetUserStatus) // 更新用户状态 + userRouter.GET(":id", userApi.GetUserById) // 获取用户信息 + } +} + +func (s *UserRouter) InitAppUserRouter(PublicRouter *gin.RouterGroup) { + appUserRouter := PublicRouter.Group("h5_user").Use(middleware.UserJWTAuth()) + { + appUserRouter.DELETE("login", userApi.Login) // 短信验证码登录 + appUserRouter.POST("sms/send", userApi.SendCode) // 发送短信验证码 + appUserRouter.POST("wxLogin", userApi.WechatLogin) // 微信登录 + appUserRouter.POST("pwdlogin", userApi.PwdLogin) // 密码登录 + appUserRouter.GET(":id", userApi.GetUserById) // 获取用户信息 } - { - userRouterWithoutAuth.DELETE("login", userApi.Login) // 短信验证码登录 - userRouterWithoutAuth.POST("sms", userApi.SendCode) // 发送短信验证码 - } - } diff --git a/service/user/user.go b/service/user/user.go index cccc94c..387b00f 100644 --- a/service/user/user.go +++ b/service/user/user.go @@ -7,15 +7,21 @@ import ( "git.echol.cn/loser/lckt/model/user" "git.echol.cn/loser/lckt/model/user/request" "git.echol.cn/loser/lckt/utils/sms" + "github.com/ArtisanCloud/PowerSocialite/v3/src/providers" "go.uber.org/zap" + "golang.org/x/crypto/bcrypt" "math/rand" "time" ) type UserService struct{} -func (u *UserService) GetUserById(id int) (user user.User, err error) { - +func (u *UserService) GetUserById(id string) (user user.User, err error) { + err = global.GVA_DB.Where("id = ?", id).First(&user).Error + if err != nil { + global.GVA_LOG.Error("查询用户失败", zap.Error(err)) + return + } return } @@ -61,7 +67,8 @@ func (u *UserService) Login(req request.CodeLoginReq) (users user.User, err erro // 2. 如果用户不存在,则创建用户 if count == 0 { user := user.User{ - Phone: req.Phone, + Phone: req.Phone, + UserLabel: 1, } err = global.GVA_DB.Save(&user).Error if err != nil { @@ -79,7 +86,8 @@ func (u *UserService) Login(req request.CodeLoginReq) (users user.User, err erro return } -func (u *UserService) GetUserList(p request.GetUserListReq) (userList user.User, total int64, err error) { +// GetUserList 获取用户列表 +func (u *UserService) GetUserList(p request.GetUserListReq) (userList []user.User, total int64, err error) { limit := p.PageSize offset := p.PageSize * (p.Page - 1) // 创建db @@ -91,6 +99,9 @@ func (u *UserService) GetUserList(p request.GetUserListReq) (userList user.User, if p.UserLabel != "" { db = db.Where("user_label = ?", p.UserLabel) } + if p.Status != 0 { + db = db.Where("status = ?", p.Status) + } err = db.Count(&total).Error if err != nil { @@ -105,6 +116,7 @@ func (u *UserService) GetUserList(p request.GetUserListReq) (userList user.User, } +// SetBalance 设置用户余额 func (u *UserService) SetBalance(p request.SetBalanceReq) (err error) { err = global.GVA_DB.Model(&user.User{}).Where("id = ?", p.Id).Update("balance", p.Balance).Error if err != nil { @@ -113,3 +125,119 @@ func (u *UserService) SetBalance(p request.SetBalanceReq) (err error) { } return } + +// WechatLogin 微信登录 +func (u *UserService) WechatLogin(info *providers.User) (users user.User, err error) { + openID := info.GetOpenID() + var count int64 + // 1. 判断用户是否存在 + err = global.GVA_DB.Model(&users).Where("open_id = ?", openID).Count(&count).Error + if err != nil { + global.GVA_LOG.Error("查询用户失败", zap.Error(err)) + return + } + + if count == 0 { + newUser := user.User{ + OpenId: openID, + NickName: info.GetNickname(), + Avatar: info.GetAvatar(), + } + err = global.GVA_DB.Save(&newUser).Error + if err != nil { + global.GVA_LOG.Error("创建用户失败", zap.Error(err)) + return + } + return newUser, nil + } else { + err = global.GVA_DB.Where("open_id = ?", openID).First(&users).Error + if err != nil { + global.GVA_LOG.Error("查询用户失败", zap.Error(err)) + return + } + } + return +} + +// PwdLogin 用户密码登录 +func (u *UserService) PwdLogin(req request.PwdLoginReq) (users user.User, err error) { + // 1. 判断用户是否存在 + var count int64 + err = global.GVA_DB.Model(&user.User{}).Where("phone = ?", req.Phone).First(&users).Count(&count).Error + if err != nil { + global.GVA_LOG.Error("查询用户失败", zap.Error(err)) + return + } + if count == 0 { + global.GVA_LOG.Error("用户不存在") + err = fmt.Errorf("用户不存在") + return + } + + // 2. 判断密码是否正确 + err = bcrypt.CompareHashAndPassword([]byte(users.Password), []byte(req.Password)) + if err != nil { + global.GVA_LOG.Error("密码错误", zap.Error(err)) + return + } + return +} + +// Register 用户注册-后台用 +func (u *UserService) Register(req request.RegisterReq) (err error) { + // 1. 判断用户是否存在 + var count int64 + err = global.GVA_DB.Model(&user.User{}).Where("phone = ?", req.Phone).Count(&count).Error + if err != nil { + global.GVA_LOG.Error("查询用户失败", zap.Error(err)) + return + } + if count == 0 { + var password []byte + password, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + global.GVA_LOG.Error("密码加密失败", zap.Error(err)) + return + } + user := user.User{ + Phone: req.Phone, + Password: string(password), + NickName: req.NickName, + UserLabel: req.UserLabel, + Status: 1, + UserType: req.UserType, + } + + err = global.GVA_DB.Save(&user).Error + if err != nil { + global.GVA_LOG.Error("创建用户失败", zap.Error(err)) + return + } + return + } else { + err = fmt.Errorf("用户已存在") + } + + return +} + +// SetUserStatus 设置用户状态 +func (u *UserService) SetUserStatus(id string) (err error) { + user := user.User{} + + err = global.GVA_DB.Model(&user).Where("id = ?", id).First(&user).Error + if err != nil { + global.GVA_LOG.Error("查询用户状态失败", zap.Error(err)) + return + } + if user.Status == 1 { + err = global.GVA_DB.Model(&user).Where("id = ?", id).Update("status", 0).Error + } else { + err = global.GVA_DB.Model(&user).Where("id = ?", id).Update("status", 1).Error + } + if err != nil { + global.GVA_LOG.Error("设置用户状态失败", zap.Error(err)) + return + } + return +}