diff --git a/api/v1/user/user.go b/api/v1/user/user.go index 9fa3167..9757d13 100644 --- a/api/v1/user/user.go +++ b/api/v1/user/user.go @@ -83,3 +83,50 @@ func (*APPUserApi) Login(ctx *gin.Context) { r.FailWithMessage("设置登录状态失败", ctx) } } + +// WechatLogin 微信登录 +func (*APPUserApi) WechatLogin(ctx *gin.Context) { + var p request.CodeLoginReq + if err := ctx.ShouldBind(&p); err != nil { + r.FailWithMessage(err.Error(), ctx) + global.GVA_LOG.Error("参数错误,登录失败", zap.Error(err)) + return + } + //Todo 待完善微信登录 +} + +// GetUserList 获取用户列表 +func (*APPUserApi) GetUserList(ctx *gin.Context) { + var p request.GetUserListReq + if err := ctx.ShouldBind(&p); err != nil { + r.FailWithMessage(err.Error(), ctx) + global.GVA_LOG.Error("参数错误,获取用户列表失败", zap.Error(err)) + return + } + userList, total, err := userService.GetUserList(p) + if err != nil { + r.FailWithMessage("获取用户列表失败", ctx) + return + } + r.OkWithDetailed(r.PageResult{ + List: userList, + Total: total, + Page: p.Page, + PageSize: p.PageSize, + }, "获取用户列表成功", ctx) +} + +// SetBalance 设置用户余额 +func (*APPUserApi) SetBalance(ctx *gin.Context) { + var p request.SetBalanceReq + if err := ctx.ShouldBind(&p); err != nil { + r.FailWithMessage(err.Error(), ctx) + global.GVA_LOG.Error("参数错误,设置用户余额失败", zap.Error(err)) + return + } + if err := userService.SetBalance(p); err != nil { + r.FailWithMessage("设置用户余额失败", ctx) + return + } + r.OkWithMessage("设置用户余额成功", ctx) +} diff --git a/config.yaml b/config.yaml index ecf6a2c..9e36e33 100644 --- a/config.yaml +++ b/config.yaml @@ -241,16 +241,17 @@ zap: retention-day: -1 wechat: - app-id: - app-secret: - token: - aes-key: + app-id: wx3d21df18d7f8f9fc + app-secret: ffce59a9a9272c1aaee53950e96617d8 + token: kjeldcsdz2phfwfxnevsajnzsboho1ev + aes-key: PiqqlGdEblw5Gv1RJ5qcTnhKUjFw9YNkBMAX6CIw6Me + callback: https://api.gin-vue-admin.com/wechat/callback pay-list: - type: wxpay alias-name: wxpay-1 app-id: 1 mch-id: 1 - v3-key: 1 + v3-key: 1a3sd8561d5179Df152D4789aD38wG9s cert-path: 1 key-path: 1 notify-url: 1 diff --git a/initialize/gorm_biz.go b/initialize/gorm_biz.go index 37d7e84..11c21b4 100644 --- a/initialize/gorm_biz.go +++ b/initialize/gorm_biz.go @@ -5,6 +5,7 @@ import ( "git.echol.cn/loser/lckt/model/article" "git.echol.cn/loser/lckt/model/bot" "git.echol.cn/loser/lckt/model/category" + "git.echol.cn/loser/lckt/model/user" ) func bizModel() error { @@ -13,6 +14,7 @@ func bizModel() error { category.Category{}, bot.Bot{}, article.Article{}, + user.User{}, ) if err != nil { return err diff --git a/initialize/router_biz.go b/initialize/router_biz.go index 45a4340..3e6fa1e 100644 --- a/initialize/router_biz.go +++ b/initialize/router_biz.go @@ -25,4 +25,8 @@ func initBizRouter(routers ...*gin.RouterGroup) { articleRouter := router.RouterGroupApp.Article articleRouter.InitBotRouter(privateGroup, publicGroup) } + { + userRouter := router.RouterGroupApp.User + userRouter.InitUserRouter(privateGroup, publicGroup) + } } diff --git a/middleware/user_jwt.go b/middleware/user_jwt.go new file mode 100644 index 0000000..8d461e7 --- /dev/null +++ b/middleware/user_jwt.go @@ -0,0 +1,64 @@ +package middleware + +import ( + "errors" + "git.echol.cn/loser/lckt/global" + "git.echol.cn/loser/lckt/utils" + "github.com/golang-jwt/jwt/v5" + "strconv" + "time" + + "git.echol.cn/loser/lckt/model/common/response" + "github.com/gin-gonic/gin" +) + +func UserJWTAuth() gin.HandlerFunc { + return func(c *gin.Context) { + // 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录 + token := utils.GetToken(c) + if token == "" { + response.NoAuth("未登录或非法访问", c) + c.Abort() + return + } + + j := utils.NewJWT() + // parseToken 解析token包含的信息 + claims, err := j.ParseToken(token) + if err != nil { + if errors.Is(err, utils.TokenExpired) { + response.NoAuth("授权已过期", c) + utils.ClearToken(c) + c.Abort() + return + } + response.NoAuth(err.Error(), c) + utils.ClearToken(c) + c.Abort() + return + } + + c.Set("claims", claims) + if claims.ExpiresAt.Unix()-time.Now().Unix() < claims.BufferTime { + dr, _ := utils.ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime) + claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(dr)) + newToken, _ := j.CreateTokenByOldToken(token, *claims) + newClaims, _ := j.ParseToken(newToken) + c.Header("new-token", newToken) + c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10)) + utils.SetToken(c, newToken, int(dr.Seconds())) + if global.GVA_CONFIG.System.UseMultipoint { + // 记录新的活跃jwt + _ = jwtService.SetRedisJWT(newToken, newClaims.Username) + } + } + c.Next() + + if newToken, exists := c.Get("new-token"); exists { + c.Header("new-token", newToken.(string)) + } + if newExpiresAt, exists := c.Get("new-expires-at"); exists { + c.Header("new-expires-at", newExpiresAt.(string)) + } + } +} diff --git a/model/user/request/user.go b/model/user/request/user.go index 71a93a4..4eed1cd 100644 --- a/model/user/request/user.go +++ b/model/user/request/user.go @@ -1,5 +1,7 @@ package request +import "git.echol.cn/loser/lckt/model/common/request" + type SendCodeReq struct { Phone string `json:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"` } @@ -32,11 +34,18 @@ type BindWechatReq struct { UnionId string `json:"unionId" gorm:"type:varchar(64);comment:用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的 unionid 是唯一的。"` } -type BindQQReq struct { - QQ string `json:"qq" vd:"@:len($)>0; msg:'QQ号不能为空'"` -} - type BindPhoneReq struct { Id int `json:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"` Phone string `json:"phone" vd:"@:len($)>0; msg:'手机号码不能为空'"` } + +type GetUserListReq struct { + request.PageInfo + Type int `json:"type"` + UserLabel string `json:"user_label" ` +} + +type SetBalanceReq struct { + Id int `json:"id" vd:"@:len($)>0; msg:'用户ID不能为空'"` + Balance float32 `json:"balance" vd:"@:len($)>0; msg:'余额不能为空'"` +} diff --git a/router/enter.go b/router/enter.go index 1870450..60dfc04 100644 --- a/router/enter.go +++ b/router/enter.go @@ -6,6 +6,7 @@ import ( "git.echol.cn/loser/lckt/router/category" "git.echol.cn/loser/lckt/router/example" "git.echol.cn/loser/lckt/router/system" + "git.echol.cn/loser/lckt/router/user" ) var RouterGroupApp = new(RouterGroup) @@ -16,4 +17,5 @@ type RouterGroup struct { Category category.RouterGroup Bot bot.RouterGroup Article article.RouterGroup + User user.UserRouter } diff --git a/router/user/enter.go b/router/user/enter.go new file mode 100644 index 0000000..5e7a200 --- /dev/null +++ b/router/user/enter.go @@ -0,0 +1,9 @@ +package user + +import ( + api "git.echol.cn/loser/lckt/api/v1" +) + +type RouterGroup struct{ UserRouter } + +var userApi = api.ApiGroupApp.UserApiGroup diff --git a/router/user/user.go b/router/user/user.go new file mode 100644 index 0000000..af860d6 --- /dev/null +++ b/router/user/user.go @@ -0,0 +1,23 @@ +package user + +import ( + "git.echol.cn/loser/lckt/middleware" + "github.com/gin-gonic/gin" +) + +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) // 更新用户余额 + } + { + userRouterWithoutAuth.DELETE("login", userApi.Login) // 短信验证码登录 + userRouterWithoutAuth.POST("sms", userApi.SendCode) // 发送短信验证码 + } + +} diff --git a/service/user/user.go b/service/user/user.go index 861ddf3..cccc94c 100644 --- a/service/user/user.go +++ b/service/user/user.go @@ -78,3 +78,38 @@ 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) { + limit := p.PageSize + offset := p.PageSize * (p.Page - 1) + // 创建db + db := global.GVA_DB.Model(&user.User{}) + // 如果有条件搜索 下方会自动创建搜索语句 + if p.Type != 0 { + db = db.Where("user_type = ?", p.Type) + } + if p.UserLabel != "" { + db = db.Where("user_label = ?", p.UserLabel) + } + + err = db.Count(&total).Error + if err != nil { + return + } + + if limit != 0 { + db = db.Limit(limit).Offset(offset) + } + err = db.Find(&userList).Error + return + +} + +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 { + global.GVA_LOG.Error("设置用户余额失败", zap.Error(err)) + return + } + return +} diff --git a/utils/claims.go b/utils/claims.go index c08c90c..2ef0dbd 100644 --- a/utils/claims.go +++ b/utils/claims.go @@ -19,9 +19,9 @@ func ClearToken(c *gin.Context) { } if net.ParseIP(host) != nil { - c.SetCookie("x-token", "", -1, "/", "", false, false) + c.SetCookie("app-token", "", -1, "/", "", false, false) } else { - c.SetCookie("x-token", "", -1, "/", host, false, false) + c.SetCookie("app-token", "", -1, "/", host, false, false) } } @@ -33,17 +33,17 @@ func SetToken(c *gin.Context, token string, maxAge int) { } if net.ParseIP(host) != nil { - c.SetCookie("x-token", token, maxAge, "/", "", false, false) + c.SetCookie("app-token", token, maxAge, "/", "", false, false) } else { - c.SetCookie("x-token", token, maxAge, "/", host, false, false) + c.SetCookie("app-token", token, maxAge, "/", host, false, false) } } func GetToken(c *gin.Context) string { - token := c.Request.Header.Get("x-token") + token := c.Request.Header.Get("app-token") if token == "" { j := NewJWT() - token, _ = c.Cookie("x-token") + token, _ = c.Cookie("app-token") claims, err := j.ParseToken(token) if err != nil { global.GVA_LOG.Error("重新写入cookie token失败,未能成功解析token,请检查请求头是否存在x-token且claims是否为规定结构")