feat(user): 实现基础用户系统

- 实现用户手机验证码、密码登录功能,支持 JWT 认证
- 实现用户注册功能
- 实现jwt中获取用户信息
This commit is contained in:
liangzai 2024-11-03 19:06:09 +08:00
parent cca66faed5
commit d09e7a08cf
23 changed files with 168 additions and 246 deletions

View File

@ -10,7 +10,7 @@ info (
type ( type (
sendSmsReq { sendSmsReq {
Mobile string `json:"mobile" validate:"required,mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
actionType string `json:"actionType" validate:"required"` ActionType string `json:"actionType" validate:"required,oneof=loginCode registerCode QueryCode"`
} }
) )

View File

@ -24,9 +24,13 @@ service user {
@handler register @handler register
post /user/register (RegisterReq) returns (RegisterResp) post /user/register (RegisterReq) returns (RegisterResp)
@doc "login" @doc "mobile login"
@handler login @handler mobileLogin
post /user/login (LoginReq) returns (LoginResp) post /user/mobileLogin (MobileLoginReq) returns (MobileLoginResp)
@doc "mobile code login"
@handler mobileCodeLogin
post /user/mobileCodeLogin (MobileCodeLoginReq) returns (MobileCodeLoginResp)
} }
//need login //need login

View File

@ -8,14 +8,16 @@ info (
) )
type User { type User {
Id int64 `json:"id"` Id int64 `json:"id"`
Mobile string `json:"mobile"` Mobile string `json:"mobile"`
NickName string `json:"nickName"`
} }
type ( type (
RegisterReq { RegisterReq {
Mobile string `json:"mobile" validate:"required,mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
Password string `json:"password" validate:"required"` Password string `json:"password" validate:"required,min=11,max=11,password"`
Code string `json:"code" validate:"required"`
} }
RegisterResp { RegisterResp {
AccessToken string `json:"accessToken"` AccessToken string `json:"accessToken"`
@ -25,11 +27,23 @@ type (
) )
type ( type (
LoginReq { MobileLoginReq {
Mobile string `json:"mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
Password string `json:"password"` Password string `json:"password" validate:"required"`
} }
LoginResp { MobileLoginResp {
AccessToken string `json:"accessToken"`
AccessExpire int64 `json:"accessExpire"`
RefreshAfter int64 `json:"refreshAfter"`
}
)
type (
MobileCodeLoginReq {
Mobile string `json:"mobile"`
Code string `json:"code" validate:"required"`
}
MobileCodeLoginResp {
AccessToken string `json:"accessToken"` AccessToken string `json:"accessToken"`
AccessExpire int64 `json:"accessExpire"` AccessExpire int64 `json:"accessExpire"`
RefreshAfter int64 `json:"refreshAfter"` RefreshAfter int64 `json:"refreshAfter"`

View File

@ -1,10 +1,10 @@
Name: user Name: user
Host: 0.0.0.0 Host: 0.0.0.0
Port: 8888 Port: 8888
DataSource: "qnc:V3j@K9!qL5#wX4$p@tcp(127.0.0.1:20001)/qnc?charset=utf8mb4&parseTime=True&loc=Local" DataSource: "qnc:5vg67b3UNHu8@tcp(127.0.0.1:20001)/qnc?charset=utf8mb4&parseTime=True&loc=Local"
CacheRedis: CacheRedis:
- Host: "127.0.0.1:20002" - Host: "127.0.0.1:20002"
Pass: "A!02Myl>H}ZQfpxC" # Redis 密码,如果未设置则留空 Pass: "3m3WsgyCKWqz" # Redis 密码,如果未设置则留空
Type: "node" # 单节点模式 Type: "node" # 单节点模式
JwtAuth: JwtAuth:
AccessSecret: "WUvoIwL-FK0qnlxhvxR9tV6SjfOpeJMpKmY2QvT99lA" AccessSecret: "WUvoIwL-FK0qnlxhvxR9tV6SjfOpeJMpKmY2QvT99lA"

View File

@ -1,29 +0,0 @@
package auth
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"qnc-server/app/user/cmd/api/internal/logic/auth"
"qnc-server/app/user/cmd/api/internal/svc"
"qnc-server/app/user/cmd/api/internal/types"
"qnc-server/common/result"
"qnc-server/pkg/lzkit/validator"
)
func SendSmsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.SendSmsReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := auth.NewSendSmsLogic(r.Context(), svcCtx)
err := l.SendSms(&req)
result.HttpResult(r, w, nil, err)
}
}

View File

@ -27,10 +27,16 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes( server.AddRoutes(
[]rest.Route{ []rest.Route{
{ {
// login // mobile code login
Method: http.MethodPost, Method: http.MethodPost,
Path: "/user/login", Path: "/user/mobileCodeLogin",
Handler: user.LoginHandler(serverCtx), Handler: user.MobileCodeLoginHandler(serverCtx),
},
{
// mobile login
Method: http.MethodPost,
Path: "/user/mobileLogin",
Handler: user.MobileLoginHandler(serverCtx),
}, },
{ {
// register // register

View File

@ -1,29 +0,0 @@
package user
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"qnc-server/app/user/cmd/api/internal/logic/user"
"qnc-server/app/user/cmd/api/internal/svc"
"qnc-server/app/user/cmd/api/internal/types"
"qnc-server/common/result"
"qnc-server/pkg/lzkit/validator"
)
func DetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UserInfoReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := user.NewDetailLogic(r.Context(), svcCtx)
resp, err := l.Detail(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@ -1,29 +0,0 @@
package user
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"qnc-server/app/user/cmd/api/internal/logic/user"
"qnc-server/app/user/cmd/api/internal/svc"
"qnc-server/app/user/cmd/api/internal/types"
"qnc-server/common/result"
"qnc-server/pkg/lzkit/validator"
)
func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.LoginReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := user.NewLoginLogic(r.Context(), svcCtx)
resp, err := l.Login(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@ -1,29 +0,0 @@
package user
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"qnc-server/app/user/cmd/api/internal/logic/user"
"qnc-server/app/user/cmd/api/internal/svc"
"qnc-server/app/user/cmd/api/internal/types"
"qnc-server/common/result"
"qnc-server/pkg/lzkit/validator"
)
func RegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.RegisterReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := user.NewRegisterLogic(r.Context(), svcCtx)
resp, err := l.Register(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@ -1,29 +0,0 @@
package user
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"qnc-server/app/user/cmd/api/internal/logic/user"
"qnc-server/app/user/cmd/api/internal/svc"
"qnc-server/app/user/cmd/api/internal/types"
"qnc-server/common/result"
"qnc-server/pkg/lzkit/validator"
)
func WxMiniAuthHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.WXMiniAuthReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := user.NewWxMiniAuthLogic(r.Context(), svcCtx)
resp, err := l.WxMiniAuth(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@ -37,12 +37,12 @@ func (l *SendSmsLogic) SendSms(req *types.SendSmsReq) error {
redisKey := fmt.Sprintf("%s:%s", req.ActionType, req.Mobile) redisKey := fmt.Sprintf("%s:%s", req.ActionType, req.Mobile)
exists, err := l.svcCtx.Redis.Exists(redisKey) exists, err := l.svcCtx.Redis.Exists(redisKey)
if err != nil { if err != nil {
return err return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 读取redis缓存失败: %s", req.Mobile)
} }
if exists { if exists {
// 如果 Redis 中已经存在标记,说明在 1 分钟内请求过,返回错误 // 如果 Redis 中已经存在标记,说明在 1 分钟内请求过,返回错误
return errors.Wrapf(xerr.NewErrMsg("一分钟内不能重复发送验证码"), "手机号1分钟内重复请求发送二维码, 手机号: %s", req.Mobile) return errors.Wrapf(xerr.NewErrMsg("一分钟内不能重复发送验证码"), "短信发送, 手机号1分钟内重复请求发送二维码: %s", req.Mobile)
} }
code := fmt.Sprintf("%06d", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(1000000)) code := fmt.Sprintf("%06d", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(1000000))
@ -50,21 +50,21 @@ func (l *SendSmsLogic) SendSms(req *types.SendSmsReq) error {
// 发送短信 // 发送短信
smsResp, err := l.sendSmsRequest(req.Mobile, code) smsResp, err := l.sendSmsRequest(req.Mobile, code)
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送失败, 调用阿里客户端失败: %+v", err) return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 调用阿里客户端失败: %+v", err)
} }
if *smsResp.Body.Code != "OK" { if *smsResp.Body.Code != "OK" {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送失败, 阿里客户端响应失败: %s", *smsResp.Body.Message) return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 阿里客户端响应失败: %s", *smsResp.Body.Message)
} }
// 将验证码保存到 Redis设置过期时间 // 将验证码保存到 Redis设置过期时间
err = l.svcCtx.Redis.Setex(req.Mobile, code, l.svcCtx.Config.VerifyCode.ValidTime) // 验证码有效期5分钟 err = l.svcCtx.Redis.Setex(req.Mobile, code, l.svcCtx.Config.VerifyCode.ValidTime) // 验证码有效期5分钟
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "缓存失败, 验证码设置过期时间失败: %+v", err) return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 验证码设置过期时间失败: %+v", err)
} }
// 在 Redis 中设置 1 分钟的标记,限制重复请求 // 在 Redis 中设置 1 分钟的标记,限制重复请求
err = l.svcCtx.Redis.Setex(redisKey, code, 60) // 标记 1 分钟内不能重复请求 err = l.svcCtx.Redis.Setex(redisKey, code, 60) // 标记 1 分钟内不能重复请求
if err != nil { if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "缓存失败, 验证码设置限制重复请求失败: %+v", err) return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 验证码设置限制重复请求失败: %+v", err)
} }
return nil return nil
} }

View File

@ -2,9 +2,12 @@ package user
import ( import (
"context" "context"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"qnc-server/app/user/cmd/api/internal/svc" "qnc-server/app/user/cmd/api/internal/svc"
"qnc-server/app/user/cmd/api/internal/types" "qnc-server/app/user/cmd/api/internal/types"
"qnc-server/common/ctxdata"
"qnc-server/common/xerr"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
@ -24,7 +27,20 @@ func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailLogi
} }
func (l *DetailLogic) Detail(req *types.UserInfoReq) (resp *types.UserInfoResp, err error) { func (l *DetailLogic) Detail(req *types.UserInfoReq) (resp *types.UserInfoResp, err error) {
// todo: add your logic here and delete this line userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, %+v", err)
}
user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "用户信息, 数据库查询用户信息失败, %+v", err)
}
var userInfo types.User
err = copier.Copy(&userInfo, user)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户信息, 用户信息结构体复制失败, %+v", err)
}
return &types.UserInfoResp{
UserInfo: userInfo,
}, nil
} }

View File

@ -1,30 +0,0 @@
package user
import (
"context"
"qnc-server/app/user/cmd/api/internal/svc"
"qnc-server/app/user/cmd/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type LoginLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic {
return &LoginLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *LoginLogic) Login(req *types.LoginReq) (resp *types.LoginResp, err error) {
// todo: add your logic here and delete this line
return
}

View File

@ -2,7 +2,9 @@ package user
import ( import (
"context" "context"
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/stores/sqlx"
"qnc-server/app/user/cmd/api/internal/svc" "qnc-server/app/user/cmd/api/internal/svc"
"qnc-server/app/user/cmd/api/internal/types" "qnc-server/app/user/cmd/api/internal/types"
@ -14,8 +16,6 @@ import (
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
var ErrUserAlreadyRegisterError = xerr.NewErrMsg("该手机号码已注册")
type RegisterLogic struct { type RegisterLogic struct {
logx.Logger logx.Logger
ctx context.Context ctx context.Context
@ -31,30 +31,42 @@ func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Register
} }
func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterResp, err error) { func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterResp, err error) {
// 检查手机号是否在一分钟内已发送过验证码
redisKey := fmt.Sprintf("%s:%s", "registerCode", req.Mobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil {
if errors.Is(err, redis.Nil) {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "手机注册, 验证码过期: %s", req.Mobile)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 读取验证码redis缓存失败, mobile: %s, err: %+v", req.Mobile, err)
}
if cacheCode != req.Code {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机注册, 验证码不正确: %s", req.Mobile)
}
hasUser, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, req.Mobile) hasUser, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, req.Mobile)
if findUserErr != nil && findUserErr != model.ErrNotFound { if findUserErr != nil && findUserErr != model.ErrNotFound {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "mobile:%s,err:%v", req.Mobile, err) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 读取数据库获取用户失败, mobile%s, err: %+v", req.Mobile, err)
} }
if hasUser != nil { if hasUser != nil {
return nil, errors.Wrapf(ErrUserAlreadyRegisterError, "Register user exists mobile:%s,err:%v", req.Mobile, err) return nil, errors.Wrapf(xerr.NewErrMsg("该手机号码已注册"), "手机注册, 手机号码已注册, mobile:%s", req.Mobile)
} }
var userId int64 var userId int64
if transErr := l.svcCtx.UserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error { if transErr := l.svcCtx.UserModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
user := new(model.User) user := new(model.User)
user.Mobile = req.Mobile user.Mobile = req.Mobile
if len(user.Nickname) == 0 { if len(user.Nickname) == 0 {
user.Nickname = tool.Krand(8, tool.KC_RAND_KIND_ALL) user.Nickname = req.Mobile
} }
if len(req.Password) > 0 { if len(req.Password) > 0 {
user.Password = tool.Md5ByString(req.Password) user.Password = tool.Md5ByString(req.Password)
} }
insertResult, userInsertErr := l.svcCtx.UserModel.Insert(ctx, session, user) insertResult, userInsertErr := l.svcCtx.UserModel.Insert(ctx, session, user)
if userInsertErr != nil { if userInsertErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Register db user Insert err:%v,user:%+v", userInsertErr, user) return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 数据库插入新用户失败, mobile%s, err: %+v", req.Mobile, err)
} }
lastId, lastInsertIdErr := insertResult.LastInsertId() lastId, lastInsertIdErr := insertResult.LastInsertId()
if lastInsertIdErr != nil { if lastInsertIdErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Register db user insertResult.LastInsertId err:%v,user:%+v", lastInsertIdErr, user) return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 获取新用户ID失败, err:%+v, user:%+v", lastInsertIdErr, user)
} }
userId = lastId userId = lastId
@ -62,8 +74,8 @@ func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterRe
userAuth.UserId = lastId userAuth.UserId = lastId
userAuth.AuthKey = req.Mobile userAuth.AuthKey = req.Mobile
userAuth.AuthType = model.UserAuthTypeAppMobile userAuth.AuthType = model.UserAuthTypeAppMobile
if _, userAuthInsertErr := l.svcCtx.UserAuthModel.Insert(ctx, session, userAuth); err != nil { if _, userAuthInsertErr := l.svcCtx.UserAuthModel.Insert(ctx, session, userAuth); userAuthInsertErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Register db user_auth Insert err:%v,userAuth:%v", err, userAuthInsertErr) return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机注册, 数据库插入用户认证失败, err:%+v", userAuthInsertErr)
} }
return nil return nil
}); transErr != nil { }); transErr != nil {
@ -72,9 +84,8 @@ func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterRe
token, generaErr := jwtx.GenerateJwtToken(userId, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire) token, generaErr := jwtx.GenerateJwtToken(userId, l.svcCtx.Config.JwtAuth.AccessSecret, l.svcCtx.Config.JwtAuth.AccessExpire)
if generaErr != nil { if generaErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "GenerateToken error userId : %d", userId) return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机注册, 生成jwt token失败, userid: %d, err:%+v", userId, generaErr)
} }
return &types.RegisterResp{ return &types.RegisterResp{
AccessToken: token, AccessToken: token,
AccessExpire: l.svcCtx.Config.JwtAuth.AccessExpire, AccessExpire: l.svcCtx.Config.JwtAuth.AccessExpire,

View File

@ -1,12 +1,23 @@
// Code generated by goctl. DO NOT EDIT. // Code generated by goctl. DO NOT EDIT.
package types package types
type LoginReq struct { type MobileCodeLoginReq struct {
Mobile string `json:"mobile"` Mobile string `json:"mobile"`
Password string `json:"password"` Code string `json:"code" validate:"required"`
} }
type LoginResp struct { type MobileCodeLoginResp struct {
AccessToken string `json:"accessToken"`
AccessExpire int64 `json:"accessExpire"`
RefreshAfter int64 `json:"refreshAfter"`
}
type MobileLoginReq struct {
Mobile string `json:"mobile" validate:"required,mobile"`
Password string `json:"password" validate:"required"`
}
type MobileLoginResp struct {
AccessToken string `json:"accessToken"` AccessToken string `json:"accessToken"`
AccessExpire int64 `json:"accessExpire"` AccessExpire int64 `json:"accessExpire"`
RefreshAfter int64 `json:"refreshAfter"` RefreshAfter int64 `json:"refreshAfter"`
@ -14,7 +25,8 @@ type LoginResp struct {
type RegisterReq struct { type RegisterReq struct {
Mobile string `json:"mobile" validate:"required,mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
Password string `json:"password" validate:"required"` Password string `json:"password" validate:"required,min=11,max=11,password"`
Code string `json:"code" validate:"required"`
} }
type RegisterResp struct { type RegisterResp struct {
@ -24,8 +36,9 @@ type RegisterResp struct {
} }
type User struct { type User struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Mobile string `json:"mobile"` Mobile string `json:"mobile"`
NickName string `json:"nickName"`
} }
type UserInfoReq struct { type UserInfoReq struct {
@ -49,5 +62,5 @@ type WXMiniAuthResp struct {
type SendSmsReq struct { type SendSmsReq struct {
Mobile string `json:"mobile" validate:"required,mobile"` Mobile string `json:"mobile" validate:"required,mobile"`
ActionType string `json:"actionType" validate:"required"` ActionType string `json:"actionType" validate:"required,oneof=loginCode registerCode QueryCode"`
} }

View File

@ -3,20 +3,23 @@ package ctxdata
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/zeromicro/go-zero/core/logx" "fmt"
) )
// CtxKeyJwtUserId get uid from ctx const CtxKeyJwtUserId = "userId"
var CtxKeyJwtUserId = "jwtUserId"
func GetUidFromCtx(ctx context.Context) int64 { // GetUidFromCtx 从 context 中获取用户 ID
var uid int64 func GetUidFromCtx(ctx context.Context) (int64, error) {
if jsonUid, ok := ctx.Value(CtxKeyJwtUserId).(json.Number); ok { // 尝试从上下文中获取 jwtUserId
if int64Uid, err := jsonUid.Int64(); err == nil { jsonUid, ok := ctx.Value(CtxKeyJwtUserId).(json.Number)
uid = int64Uid if !ok {
} else { return 0, fmt.Errorf("无法获取用户 ID, CtxKeyJwtUserId = %v", CtxKeyJwtUserId)
logx.WithContext(ctx).Errorf("GetUidFromCtx err : %+v", err)
}
} }
return uid // 转换为 int64
uid, err := jsonUid.Int64()
if err != nil {
return 0, fmt.Errorf("用户 ID 转换失败: %+v", err)
}
return uid, nil
} }

View File

View File

@ -9,10 +9,10 @@ services:
# 时区上海 - Time zone Shanghai (Change if needed) # 时区上海 - Time zone Shanghai (Change if needed)
TZ: Asia/Shanghai TZ: Asia/Shanghai
# root 密码 - root password # root 密码 - root password
MYSQL_ROOT_PASSWORD: eh:GiX%cWyiV$WF_ MYSQL_ROOT_PASSWORD: yfg87gyuYiy1
MYSQL_DATABASE: qnc MYSQL_DATABASE: qnc
MYSQL_USER: qnc MYSQL_USER: qnc
MYSQL_PASSWORD: V3j@K9!qL5#wX4$p MYSQL_PASSWORD: 5vg67b3UNHu8
ports: ports:
- "20001:3306" - "20001:3306"
volumes: volumes:
@ -34,7 +34,7 @@ services:
#redis容器 - Redis container #redis容器 - Redis container
redis: redis:
image: redis:6.2.5 image: redis:7.4.0
container_name: qnc_redis container_name: qnc_redis
ports: ports:
- "20002:6379" - "20002:6379"
@ -44,7 +44,7 @@ services:
volumes: volumes:
# 数据文件 - data files # 数据文件 - data files
- ./data/redis/data:/data:rw - ./data/redis/data:/data:rw
command: "redis-server --requirepass A!02Myl>H}ZQfpxC --appendonly yes" command: "redis-server --requirepass 3m3WsgyCKWqz --appendonly yes"
privileged: true privileged: true
restart: always restart: always
networks: networks:

1
go.mod
View File

@ -40,6 +40,7 @@ require (
github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/compress v1.17.9 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect

2
go.sum
View File

@ -134,6 +134,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=

View File

@ -2,15 +2,18 @@ package validator
// 定义自定义错误消息 // 定义自定义错误消息
var customMessages = map[string]string{ var customMessages = map[string]string{
"Name.min": "姓名不能少于1个字", "Name.min": "姓名不能少于1个字",
"Name.required": "姓名是必填项", "Name.required": "姓名是必填项",
"Name.name": "姓名只能包含中文", "Name.name": "姓名只能包含中文",
"Mobile.required": "手机号是必填项", "Mobile.required": "手机号是必填项",
"Mobile.min": "电话号码必须为有效的中国电话号码", "Mobile.min": "电话号码必须为有效的中国电话号码",
"Mobile.max": "电话号码必须为有效的中国电话号码", "Mobile.max": "电话号码必须为有效的中国电话号码",
"Mobile.mobile": "电话号码必须为有效的中国电话号码", "Mobile.mobile": "电话号码必须为有效的中国电话号码",
"IDCard.required": "身份证号是必填项", "IDCard.required": "身份证号是必填项",
"IDCard.idCard": "无效的身份证号码", "IDCard.idCard": "无效的身份证号码",
"Password.min": "密码不能少于8位数",
"Password.max": "密码不能超过32位数",
"password.password": "密码强度太弱",
} }
// 获取自定义错误消息 // 获取自定义错误消息

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"regexp" "regexp"
"strings"
) )
var validate *validator.Validate var validate *validator.Validate
@ -43,6 +44,16 @@ func init() {
if err := validate.RegisterValidation("mobileType", validMobileType); err != nil { if err := validate.RegisterValidation("mobileType", validMobileType); err != nil {
panic(fmt.Sprintf("注册 mobileType 验证器时发生错误: %v", err)) panic(fmt.Sprintf("注册 mobileType 验证器时发生错误: %v", err))
} }
if err := validate.RegisterValidation("password", validatePassword); err != nil {
panic(fmt.Sprintf("注册 password 验证器时发生错误: %v", err))
}
}
// 弱口令列表
var weakPasswords = []string{
"12345678", "password", "123456789", "qwerty", "123456", "letmein",
"1234567", "welcome", "abc123", "password1", "1234", "111111", "admin",
} }
// Validate 校验参数逻辑 // Validate 校验参数逻辑
@ -127,3 +138,17 @@ func validMobileType(fl validator.FieldLevel) bool {
return validTypes[mobileType] return validTypes[mobileType]
} }
// 自定义密码强度校验函数
func validatePassword(fl validator.FieldLevel) bool {
password := fl.Field().String()
// 检查密码是否在弱口令列表中
for _, weakPwd := range weakPasswords {
if strings.ToLower(password) == weakPwd {
return false
}
}
return true
}

View File

@ -1 +0,0 @@
exit status 1exit status 1exit status 1exit status 1exit status 1