feat(user): 实现基础用户系统
- 实现用户手机验证码、密码登录功能,支持 JWT 认证 - 实现用户注册功能 - 实现jwt中获取用户信息
This commit is contained in:
parent
cca66faed5
commit
d09e7a08cf
@ -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"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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"`
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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,
|
||||||
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
1
go.mod
@ -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
2
go.sum
@ -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=
|
||||||
|
@ -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": "密码强度太弱",
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取自定义错误消息
|
// 获取自定义错误消息
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
exit status 1exit status 1exit status 1exit status 1exit status 1
|
|
Loading…
Reference in New Issue
Block a user