🎨 完善用户相关接口

This commit is contained in:
loser 2025-05-09 11:16:38 +08:00
parent ed3c15fbb6
commit 159ef59749
6 changed files with 328 additions and 45 deletions

View File

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

View File

@ -28,5 +28,6 @@ func initBizRouter(routers ...*gin.RouterGroup) {
{
userRouter := router.RouterGroupApp.User
userRouter.InitUserRouter(privateGroup, publicGroup)
userRouter.InitAppUserRouter(publicGroup)
}
}

View File

@ -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:'用户标签不能为空'"`
}

View File

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

View File

@ -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) // 发送短信验证码
}
}

View File

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