package sys import ( "context" "fmt" "hotgo/internal/consts" "hotgo/internal/dao" "hotgo/internal/library/hgorm/handler" "hotgo/internal/library/location" "hotgo/internal/library/token" "hotgo/internal/model" "hotgo/internal/model/entity" "hotgo/internal/model/input/sysin" "hotgo/internal/service" "hotgo/utility/encrypt" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/grand" "golang.org/x/crypto/bcrypt" ) type sSysAppMemberAuth struct{} func NewSysAppMemberAuth() *sSysAppMemberAuth { return &sSysAppMemberAuth{} } func init() { service.RegisterSysAppMemberAuth(NewSysAppMemberAuth()) } func (s *sSysAppMemberAuth) memberModel(ctx context.Context) *gdb.Model { return handler.Model(dao.AppMember.Ctx(ctx), &handler.Option{FilterTenant: true}) } func (s *sSysAppMemberAuth) Register(ctx context.Context, in *sysin.AppMemberRegisterInp) (res *sysin.AppMemberRegisterModel, err error) { mc, wc, lc := dao.AppMember.Columns(), dao.WalletAccount.Columns(), dao.AppMemberLevel.Columns() res = new(sysin.AppMemberRegisterModel) err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { count, e := s.memberModel(ctx).Where(mc.Username, in.Username).Count() if e != nil { return gerror.Wrap(e, "检查用户名失败") } if count > 0 { return gerror.New("用户名已存在") } count, e = s.memberModel(ctx).Where(mc.Mobile, in.Mobile).Count() if e != nil { return gerror.Wrap(e, "检查手机号失败") } if count > 0 { return gerror.New("手机号已存在") } var parent *entity.AppMember if e = s.memberModel(ctx).Where(mc.InviteCode, in.InviteCode).Where(mc.MemberStatus, consts.AppMemberStatusNormal).Scan(&parent); e != nil { return gerror.Wrap(e, "查询邀请人失败") } if parent == nil { return gerror.New("邀请码不存在") } parentId, level := parent.Id, parent.Level+1 tree := parent.Tree + "tr_" + fmt.Sprint(parent.Id) + " " levelId := uint64(0) var firstLevel *entity.AppMemberLevel if e = handler.Model(dao.AppMemberLevel.Ctx(ctx), &handler.Option{FilterTenant: true}).Where(lc.TenantId, 0).Where(lc.Status, consts.StatusEnabled).OrderAsc(lc.Sort).OrderAsc(lc.Id).Scan(&firstLevel); e != nil { return gerror.Wrap(e, "查询默认会员等级失败") } if firstLevel != nil { levelId = firstLevel.Id } hashed, e := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost) if e != nil { return gerror.Wrap(e, "生成密码摘要失败") } memberNo := "MB" + gtime.Now().Format("YmdHis") + grand.S(5) inviteCode, e := newAppMemberInviteCode(ctx, s.memberModel(ctx)) if e != nil { return e } memberData := g.Map{ mc.TenantId: 0, mc.MemberNo: memberNo, mc.Username: in.Username, mc.Mobile: in.Mobile, mc.PasswordHash: string(hashed), mc.LevelId: levelId, mc.ParentId: parentId, mc.Level: level, mc.Tree: tree, mc.InviteCode: inviteCode, mc.MemberStatus: consts.AppMemberStatusNormal, mc.RiskStatus: consts.AppMemberRiskNormal, mc.AutoDispatch: consts.AppMemberDispatchManual, mc.CreditScore: 100, mc.IsTestAccount: 2, } result, e := dao.AppMember.Ctx(ctx).Fields( mc.TenantId, mc.MemberNo, mc.Username, mc.Mobile, mc.PasswordHash, mc.LevelId, mc.ParentId, mc.Level, mc.Tree, mc.InviteCode, mc.MemberStatus, mc.RiskStatus, mc.AutoDispatch, mc.CreditScore, mc.IsTestAccount, ).Data(memberData).Insert() if e != nil { return gerror.Wrap(e, "创建会员失败") } id, e := result.LastInsertId() if e != nil { return gerror.Wrap(e, "读取会员ID失败") } res.MemberId = uint64(id) res.MemberNo = memberNo if _, e = dao.WalletAccount.Ctx(ctx).Fields(wc.TenantId, wc.MemberId, wc.Currency).Data(g.Map{wc.TenantId: 0, wc.MemberId: res.MemberId, wc.Currency: "CNY"}).Insert(); e != nil { return gerror.Wrap(e, "初始化会员钱包失败") } r := ghttp.RequestFromCtx(ctx) origin, host := in.RegisterOrigin, in.RegisterHost if r != nil { if r.Header.Get("Origin") != "" { origin = r.Header.Get("Origin") } if host == "" { host = r.Host } } res.Bound, res.Domain, e = service.SysUrlPool().BindOnRegister(ctx, res.MemberId, origin, host) return e }) return } func (s *sSysAppMemberAuth) AccountLogin(ctx context.Context, in *sysin.AppMemberAccountLoginInp) (res *sysin.AppMemberLoginModel, err error) { var member *entity.AppMember defer func() { service.SysAppMemberLoginLog().Push(ctx, &sysin.AppMemberLoginLogPushInp{Member: member, LoginType: consts.AppMemberLoginTypePassword, Account: in.Account, Err: err}) }() mc := dao.AppMember.Columns() if err = s.memberModel(ctx).Where(fmt.Sprintf("(%s=? OR %s=?)", mc.Username, mc.Mobile), in.Account, in.Account).Scan(&member); err != nil { err = gerror.Wrap(err, "登录失败,请稍后重试") return } if member == nil || bcrypt.CompareHashAndPassword([]byte(member.PasswordHash), []byte(in.Password)) != nil { err = gerror.New("账号或密码错误") return } return s.finishLogin(ctx, member) } func (s *sSysAppMemberAuth) MobileLogin(ctx context.Context, in *sysin.AppMemberMobileLoginInp) (res *sysin.AppMemberLoginModel, err error) { var member *entity.AppMember defer func() { service.SysAppMemberLoginLog().Push(ctx, &sysin.AppMemberLoginLogPushInp{Member: member, LoginType: consts.AppMemberLoginTypeSms, Account: in.Mobile, Err: err}) }() mc := dao.AppMember.Columns() if err = s.memberModel(ctx).Where(mc.Mobile, in.Mobile).Scan(&member); err != nil { err = gerror.Wrap(err, "登录失败,请稍后重试") return } if member == nil { err = gerror.New("会员不存在") return } if err = service.SysSmsLog().VerifyCode(ctx, &sysin.VerifyCodeInp{Event: consts.SmsTemplateLogin, Mobile: in.Mobile, Code: in.Code}); err != nil { err = gerror.New("验证码错误或已过期") return } return s.finishLogin(ctx, member) } func (s *sSysAppMemberAuth) finishLogin(ctx context.Context, member *entity.AppMember) (res *sysin.AppMemberLoginModel, err error) { if member.MemberStatus != consts.AppMemberStatusNormal { return nil, gerror.New("会员已禁用") } if member.RiskStatus == consts.AppMemberRiskBlocked { return nil, gerror.New("风控限制登录") } if err = s.updateLastLogin(ctx, member); err != nil { return nil, err } now := gtime.Now() identity := &model.Identity{Id: int64(member.Id), Username: member.Username, App: consts.AppApi, LoginAt: now} loginToken, expires, err := token.Login(ctx, identity) if err != nil { return nil, gerror.Wrap(err, "登录失败,请稍后重试") } res = &sysin.AppMemberLoginModel{Id: member.Id, MemberNo: member.MemberNo, Username: member.Username, Mobile: encrypt.MaskMobile(member.Mobile), Token: loginToken, Expires: expires} return } func (s *sSysAppMemberAuth) updateLastLogin(ctx context.Context, member *entity.AppMember) error { r := ghttp.RequestFromCtx(ctx) if r == nil { return gerror.New("登录失败,请稍后重试") } clientIP := location.GetClientIp(r) cipher, err := encrypt.EncryptField(ctx, clientIP) if err != nil { return gerror.Wrap(err, "登录失败,请稍后重试") } region := "" if ipData, locationErr := location.GetLocation(ctx, clientIP); locationErr == nil && ipData != nil { region = formatLoginRegion(ipData) } else if locationErr != nil { g.Log().Warningf(ctx, "解析会员登录地区失败 memberId:%d ip:%s err:%+v", member.Id, maskIP(clientIP), locationErr) } mc := dao.AppMember.Columns() _, err = s.memberModel(ctx).Fields(mc.LastLoginAt, mc.LastLoginIpCipher, mc.LastLoginIpMask, mc.LastLoginRegion).WherePri(member.Id).Data(g.Map{ mc.LastLoginAt: gtime.Now(), mc.LastLoginIpCipher: cipher, mc.LastLoginIpMask: maskIP(clientIP), mc.LastLoginRegion: region, }).Update() if err != nil { return gerror.Wrap(err, "登录失败,请稍后重试") } return nil }