207 lines
7.8 KiB
Go
207 lines
7.8 KiB
Go
package sys
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"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("手机号已存在")
|
|
}
|
|
|
|
parentId, level, tree := uint64(0), 1, ""
|
|
if in.InviteCode != "" {
|
|
var parent *entity.AppMember
|
|
if e = s.memberModel(ctx).Where(mc.InviteCode, in.InviteCode).Scan(&parent); e != nil {
|
|
return gerror.Wrap(e, "查询邀请人失败")
|
|
}
|
|
if parent == nil {
|
|
return gerror.New("邀请码不存在")
|
|
}
|
|
parentId = parent.Id
|
|
level = 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)
|
|
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: strings.ToUpper(grand.S(8)),
|
|
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
|
|
}
|