From 56766912108fe0dfe8c5a81fcada0fcef7da1f42 Mon Sep 17 00:00:00 2001 From: linquan <349589071@qq.com> Date: Thu, 19 Jun 2025 18:00:19 +0800 Subject: [PATCH] login --- api/v1/system/sys_email.go | 2 +- api/v1/system/sys_login.go | 34 +++++-- email.go | 12 +-- internal/consts/consts.go | 7 -- internal/controller/sys_login.go | 154 +++++++++++++++++++++++++++---- internal/service/aliyunSms.go | 37 ++++++-- internal/service/email.go | 68 +++++++++++--- internal/service/sys_user.go | 62 +++++++++++++ 8 files changed, 319 insertions(+), 57 deletions(-) diff --git a/api/v1/system/sys_email.go b/api/v1/system/sys_email.go index 77d2532..a641176 100644 --- a/api/v1/system/sys_email.go +++ b/api/v1/system/sys_email.go @@ -4,7 +4,7 @@ import "github.com/gogf/gf/v2/frame/g" type SendEmailReq struct { g.Meta `path:"/sendEmail" tags:"email" method:"post" summary:"邮件"` - Phone string `p:"userName"` + Code string `p:"code"` } type SendEmailRes struct { diff --git a/api/v1/system/sys_login.go b/api/v1/system/sys_login.go index 1a90748..07556de 100644 --- a/api/v1/system/sys_login.go +++ b/api/v1/system/sys_login.go @@ -28,12 +28,18 @@ type UserLoginRes struct { Permissions []string `json:"permissions"` } +type UserLoginOutReq struct { + g.Meta `path:"/loginOut" tags:"登录" method:"delete" summary:"退出登录"` + Authorization string `p:"Authorization" in:"header" dc:"Bearer {{token}}"` +} + +type UserLoginOutRes struct { +} + type UserLoginMobileReq struct { - g.Meta `path:"/loginMobile" tags:"登录" method:"post" summary:"用户登录"` + g.Meta `path:"/loginMobile" tags:"登录" method:"post" summary:"用户手机登录"` Phone string `p:"userName" v:"required#手机号不能为空"` Code string `p:"code" v:"required#验证码不能为空"` - //VerifyCode string `p:"verifyCode" v:"required#验证码不能为空"` - //VerifyKey string `p:"verifyKey"` } type UserLoginMobileRes struct { @@ -52,10 +58,24 @@ type MobileCodeReq struct { type MobileCodeRes struct { } -type UserLoginOutReq struct { - g.Meta `path:"/loginOut" tags:"登录" method:"delete" summary:"退出登录"` - Authorization string `p:"Authorization" in:"header" dc:"Bearer {{token}}"` +type UserLoginEmailReq struct { + g.Meta `path:"/loginEmail" tags:"登录" method:"post" summary:"用户邮箱登录"` + Email string `p:"userName" v:"required#邮箱不能为空"` + Code string `p:"code" v:"required#验证码不能为空"` } -type UserLoginOutRes struct { +type UserLoginEmailRes struct { + g.Meta `mime:"application/json"` + UserInfo *model2.LoginUserRes `json:"userInfo"` + Token string `json:"token"` + MenuList []*model2.UserMenus `json:"menuList"` + Permissions []string `json:"permissions"` +} + +type EmailCodeReq struct { + g.Meta `path:"/emailCode" tags:"登录" method:"post" summary:"邮箱验证码"` + Email string `p:"userName" v:"required#邮箱不能为空"` +} + +type EmailCodeRes struct { } diff --git a/email.go b/email.go index 846c738..a3c0ad2 100644 --- a/email.go +++ b/email.go @@ -9,13 +9,13 @@ import ( func main() { e := email.NewEmail() - e.From = "lin <13950405063@163.com>" + e.From = "lin " e.To = []string{"349589071@qq.com"} - e.Cc = []string{"linquan13950405063@163.com"} - e.Subject = "Awesome web" - e.Text = []byte("Text Body is, of course, supported!") - err := e.Send("smtp.163.com:25", smtp.PlainAuth("", "linquan13950405063@163.com", "lin_quan120", "smtp.163.com")) + e.Bcc = []string{"linquan13950405063@163.com"} + e.Subject = "【桃源记2】验证码" + e.HTML = []byte("这是桃源记2后台登录的验证码

验证码: 713379

请在10分钟内使用。") + err := e.Send("smtp.163.com:25", smtp.PlainAuth("", "linquan13950405063@163.com", "YGefKDbTiwMRW3nM", "smtp.163.com")) if err != nil { - log.Fatal(err) + log.Print(err) } } diff --git a/internal/consts/consts.go b/internal/consts/consts.go index a00df85..9391e4b 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -97,10 +97,3 @@ const CHARSETDef = "0123456789abcdefghijklmnopqrstuvwxyz" const CHARSETSpecial = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" const DEFAULT_HOST = "127.0.0.1" - -const TEMPLATE_CODE_BIND = "SMS_205408382" // 短信模板id -const TEMPLATE_CODE_PASSWORD = "SMS_205440227" -const CODE_SIGN_NAME = "桃源记" // 签名 -const CODE_ACCESS_KEY_ID = "LTAI4GBDHSn2itPipVdt8rXu" // accessKeyId -const CODE_ACCESS_SECRET = "KOzOtz0dGyqZLpSg0QXRkGrBroRMjL" // accessSecret -const CODE_REGION_ID = "cn-hangzhou" diff --git a/internal/controller/sys_login.go b/internal/controller/sys_login.go index 94acc54..27e5b35 100644 --- a/internal/controller/sys_login.go +++ b/internal/controller/sys_login.go @@ -113,6 +113,13 @@ func (c *loginController) Login(ctx context.Context, req *system.UserLoginReq) ( return } +// LoginOut 退出登录 +func (c *loginController) LoginOut(ctx context.Context, req *system.UserLoginOutReq) (res *system.UserLoginOutRes, err error) { + err = service.SysGfToken().RemoveToken(ctx, service.SysGfToken().GetRequestToken(g.RequestFromCtx(ctx))) + return +} + +// LoginMobile 手机登录 func (c *loginController) LoginMobile(ctx context.Context, req *system.UserLoginMobileReq) (res *system.UserLoginMobileRes, err error) { var ( user *model.LoginUserRes @@ -183,11 +190,9 @@ func (c *loginController) LoginMobile(ctx context.Context, req *system.UserLogin // MobileCode 获取验证码 func (c *loginController) MobileCode(ctx context.Context, req *system.MobileCodeReq) (res *system.MobileCodeRes, err error) { log.Println("MobileCode: ", gjson.MustEncodeString(req)) - user, err := service.User().GetUserByMobile(ctx, req.Phone) - liberr.ErrIsNil(ctx, err) - liberr.ValueIsNil(user, "手机验证码错误") ip := libUtils.GetClientIp(ctx) userAgent := libUtils.GetUserAgent(ctx) + user, err := service.User().GetUserByMobile(ctx, req.Phone) if err != nil { // 保存登录失败的日志信息 service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{ @@ -200,6 +205,7 @@ func (c *loginController) MobileCode(ctx context.Context, req *system.MobileCode }) return } + liberr.ValueIsNil(user, "手机验证码错误") code, err := service.User().GetCodeByMobile(ctx, req.Phone) if err != nil { // 保存登录失败的日志信息 @@ -213,24 +219,134 @@ func (c *loginController) MobileCode(ctx context.Context, req *system.MobileCode }) return } - //err = service.AliYunSms().Main([]string{req.Phone, code}) - //if err != nil { - // // 保存登录失败的日志信息 - // service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{ - // Status: 0, - // Username: req.Phone, - // Ip: ip, - // UserAgent: userAgent, - // Msg: err.Error(), - // Module: "系统后台", - // }) - // return - //} + err = service.AliYunSms().Main([]string{req.Phone, code}) + if err != nil { + // 保存登录失败的日志信息 + service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{ + Status: 0, + Username: req.Phone, + Ip: ip, + UserAgent: userAgent, + Msg: err.Error(), + Module: "系统后台", + }) + return + } return } -// LoginOut 退出登录 -func (c *loginController) LoginOut(ctx context.Context, req *system.UserLoginOutReq) (res *system.UserLoginOutRes, err error) { - err = service.SysGfToken().RemoveToken(ctx, service.SysGfToken().GetRequestToken(g.RequestFromCtx(ctx))) +// LoginEmail 邮件登录 +func (c *loginController) LoginEmail(ctx context.Context, req *system.UserLoginEmailReq) (res *system.UserLoginEmailRes, err error) { + var ( + user *model.LoginUserRes + token string + permissions []string + menuList []*model.UserMenus + ) + + //判断验证码是否正确 + debug := gmode.IsDevelop() + if !debug { + //if !service.Captcha().VerifyString(req.VerifyKey, req.VerifyCode) { + // err = gerror.New("验证码输入错误") + // return + //} + } + ip := libUtils.GetClientIp(ctx) + userAgent := libUtils.GetUserAgent(ctx) + user, err = service.User().GetAdminUserByEmail(ctx, req) + if err != nil { + // 保存登录失败的日志信息 + service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{ + Status: 0, + Username: req.Email, + Ip: ip, + UserAgent: userAgent, + Msg: err.Error(), + Module: "系统后台", + }) + return + } + err = service.User().UpdateLoginInfo(ctx, user.Id, ip) + if err != nil { + return + } + // 报存登录成功的日志信息 + service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{ + Status: 1, + Username: req.Email, + Ip: ip, + UserAgent: userAgent, + Msg: "登录成功", + Module: "系统后台", + }) + key := gconv.String(user.Id) + "-" + gmd5.MustEncryptString(user.UserName) + gmd5.MustEncryptString(user.UserPassword) + if g.Cfg().MustGet(ctx, "gfToken.multiLogin").Bool() { + key = gconv.String(user.Id) + "-" + gmd5.MustEncryptString(user.UserName) + gmd5.MustEncryptString(user.UserPassword+ip+userAgent) + } + user.UserPassword = "" + token, err = service.SysGfToken().GenerateToken(ctx, key, user) + if err != nil { + return + } + //获取用户菜单数据 + menuList, permissions, err = service.User().GetAdminRules(ctx, user.Id) + if err != nil { + return + } + res = &system.UserLoginEmailRes{ + UserInfo: user, + Token: token, + MenuList: menuList, + Permissions: permissions, + } + return +} + +// EmailCode 获取验证码 +func (c *loginController) EmailCode(ctx context.Context, req *system.EmailCodeReq) (res *system.EmailCodeRes, err error) { + log.Println("EmailCode: ", gjson.MustEncodeString(req)) + ip := libUtils.GetClientIp(ctx) + userAgent := libUtils.GetUserAgent(ctx) + user, err := service.User().GetUserByEmail(ctx, req.Email) + if err != nil { + // 保存登录失败的日志信息 + service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{ + Status: 0, + Username: req.Email, + Ip: ip, + UserAgent: userAgent, + Msg: err.Error(), + Module: "系统后台", + }) + return + } + liberr.ValueIsNil(user, "验证码错误") + code, err := service.User().GetCodeByEmail(ctx, req.Email) + if err != nil { + // 保存登录失败的日志信息 + service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{ + Status: 0, + Username: req.Email + "_" + code, + Ip: ip, + UserAgent: userAgent, + Msg: err.Error(), + Module: "系统后台", + }) + return + } + err = service.Email().SendEmail(req.Email, code) + if err != nil { + // 保存登录失败的日志信息 + service.SysLoginLog().Invoke(ctx, &model.LoginLogParams{ + Status: 0, + Username: req.Email, + Ip: ip, + UserAgent: userAgent, + Msg: err.Error(), + Module: "系统后台", + }) + return + } return } diff --git a/internal/service/aliyunSms.go b/internal/service/aliyunSms.go index 278b00e..8af6c5f 100644 --- a/internal/service/aliyunSms.go +++ b/internal/service/aliyunSms.go @@ -10,9 +10,10 @@ import ( "github.com/alibabacloud-go/tea/tea" credential "github.com/aliyun/credentials-go/credentials" "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" "log" "strings" - "tyj_admin/internal/consts" ) type IAliYunSms interface { @@ -29,6 +30,30 @@ func AliYunSms() IAliYunSms { return &aliYunSmsService } +type AliyunSms struct { + AccessKeyId string + AccessSecret string + RegionId string + CodeSignName string + TemplateCode string +} + +var ( + smsConfig AliyunSms +) + +func init() { + //加载游戏相关配置 + ctx := gctx.New() + + smsConfig.AccessKeyId = g.Cfg().MustGet(ctx, "game.sms.accessKeyId").String() + smsConfig.AccessSecret = g.Cfg().MustGet(ctx, "game.sms.accessSecret").String() + smsConfig.RegionId = g.Cfg().MustGet(ctx, "game.sms.regionId").String() + smsConfig.CodeSignName = g.Cfg().MustGet(ctx, "game.sms.codeSignName").String() + smsConfig.TemplateCode = g.Cfg().MustGet(ctx, "game.sms.templateCodePass").String() + +} + /* Description: @@ -46,10 +71,10 @@ func (a *aliYunSmsServiceTmpl) CreateClient() (_result *dysmsapi20170525.Client, config := &openapi.Config{ Credential: credential, // 您的AccessKey ID - AccessKeyId: tea.String(consts.CODE_ACCESS_KEY_ID), + AccessKeyId: tea.String(smsConfig.AccessKeyId), // 您的AccessKey Secret - AccessKeySecret: tea.String(consts.CODE_ACCESS_SECRET), - RegionId: tea.String(consts.CODE_REGION_ID), + AccessKeySecret: tea.String(smsConfig.AccessSecret), + RegionId: tea.String(smsConfig.RegionId), } // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi config.Endpoint = tea.String("dysmsapi.aliyuncs.com") @@ -74,8 +99,8 @@ func (a *aliYunSmsServiceTmpl) Main(args []string) (_err error) { sendSmsRequest := &dysmsapi20170525.SendSmsRequest{ PhoneNumbers: tea.String(args[0]), - SignName: tea.String(consts.CODE_SIGN_NAME), - TemplateCode: tea.String(consts.TEMPLATE_CODE_PASSWORD), + SignName: tea.String(smsConfig.CodeSignName), + TemplateCode: tea.String(smsConfig.TemplateCode), TemplateParam: tea.String(gjson.MustEncodeString(map[string]string{"code": args[1]})), } diff --git a/internal/service/email.go b/internal/service/email.go index ebd156f..05577c4 100644 --- a/internal/service/email.go +++ b/internal/service/email.go @@ -1,15 +1,18 @@ package service import ( - "context" + "crypto/tls" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" "github.com/jordan-wright/email" "log" "net/smtp" - "tyj_admin/api/v1/system" + "strings" ) type IEmail interface { - SendEmail(ctx context.Context, req *system.SendEmailReq) (res *system.SendEmailRes, err error) + SendEmail(emailAddr, code string) (err error) } type emailServiceTmpl struct { @@ -21,6 +24,34 @@ func Email() IEmail { return &emailService } +type EmailTmpl struct { + From string + EmailAddr string + Password string + Smtp string + Port string + Subject string + HTML string +} + +var ( + emailConfig EmailTmpl +) + +func init() { + //加载游戏相关配置 + ctx := gctx.New() + + emailConfig.From = g.Cfg().MustGet(ctx, "game.email.from").String() + emailConfig.EmailAddr = g.Cfg().MustGet(ctx, "game.email.emailAddr").String() + emailConfig.Smtp = g.Cfg().MustGet(ctx, "game.email.smtp").String() + emailConfig.Password = g.Cfg().MustGet(ctx, "game.email.password").String() + emailConfig.Port = g.Cfg().MustGet(ctx, "game.email.port").String() + emailConfig.Subject = g.Cfg().MustGet(ctx, "game.email.subject").String() + emailConfig.HTML = g.Cfg().MustGet(ctx, "game.email.html").String() + +} + /* * Description: * @@ -28,15 +59,30 @@ func Email() IEmail { * @return Client * @throws Exception */ -func (a *emailServiceTmpl) SendEmail(ctx context.Context, req *system.SendEmailReq) (res *system.SendEmailRes, err error) { +func (a *emailServiceTmpl) SendEmail(emailAddr, code string) (err error) { e := email.NewEmail() - e.From = "lin <13950405063@163.com>" - e.To = []string{"349589071@qq.com"} - e.Subject = "Awesome web" - e.Text = []byte("Text Body is, of course, supported!") - err = e.Send("smtp.163.com:25", smtp.PlainAuth("", "linquan13950405063@163.com", "lin_quan120", "smtp.163.com")) - if err != nil { - log.Fatal(err) + + e.From = emailConfig.From + e.To = []string{emailAddr} + e.Bcc = []string{emailConfig.EmailAddr} + e.Subject = emailConfig.Subject + e.HTML = []byte(strings.Replace(emailConfig.HTML, "CODE", code, -1)) + log.Println("SendEmail: ", gjson.MustEncodeString(emailConfig)) + auth := smtp.PlainAuth("", emailConfig.EmailAddr, emailConfig.Password, emailConfig.Smtp) + if emailConfig.Port == "25" { + err = e.Send(emailConfig.Smtp+":"+emailConfig.Port, auth) + if err != nil { + log.Println("SendEmail: Send:", err) + } + } else { + tc := &tls.Config{ + ServerName: emailConfig.Smtp, + InsecureSkipVerify: true, + } + err = e.SendWithTLS(emailConfig.Smtp+":"+emailConfig.Port, auth, tc) + if err != nil { + log.Println("SendEmail: SendWithTLS: ", err) + } } return } diff --git a/internal/service/sys_user.go b/internal/service/sys_user.go index f16f548..9d88311 100644 --- a/internal/service/sys_user.go +++ b/internal/service/sys_user.go @@ -35,6 +35,7 @@ import ( type IUser interface { GetAdminUserByUsernamePassword(ctx context.Context, req *system.UserLoginReq) (user *model2.LoginUserRes, err error) GetAdminUserByPhone(ctx context.Context, req *system.UserLoginMobileReq) (user *model2.LoginUserRes, err error) + GetAdminUserByEmail(ctx context.Context, req *system.UserLoginEmailReq) (user *model2.LoginUserRes, err error) LoginLog(ctx context.Context, params *model2.LoginLogParams) UpdateLoginInfo(ctx context.Context, id uint64, ip string) (err error) NotCheckAuthAdminIds(ctx context.Context) *gset.Set @@ -49,7 +50,9 @@ type IUser interface { Delete(ctx context.Context, ids []int) (err error) GetUserByUsername(ctx context.Context, userName string) (user *model2.LoginUserRes, err error) GetUserByMobile(ctx context.Context, mobile string) (user *model2.LoginUserRes, err error) + GetUserByEmail(ctx context.Context, email string) (user *model2.LoginUserRes, err error) GetCodeByMobile(ctx context.Context, mobile string) (code string, err error) + GetCodeByEmail(ctx context.Context, email string) (code string, err error) } type userImpl struct { @@ -127,6 +130,29 @@ func (s *userImpl) GetAdminUserByPhone(ctx context.Context, req *system.UserLogi return } +func (s *userImpl) GetAdminUserByEmail(ctx context.Context, req *system.UserLoginEmailReq) (user *model2.LoginUserRes, err error) { + err = g.Try(ctx, func(ctx context.Context) { + user, err = s.GetUserByEmail(ctx, req.Email) + liberr.ErrIsNil(ctx, err) + liberr.ValueIsNil(user, "验证码错误") + log.Printf("GetAdminUserByPhone >>> Email:%s", req.Email) + //验证密码 + model, ok := codes[req.Email] + log.Printf("GetAdminUserByPhone >>> Email:%s, time %s ", req.Email, fmt.Sprint(model.Time.Unix())+"--"+fmt.Sprint(gtime.Now().Unix())) + if !ok || model.Time.Unix()+600 < gtime.Now().Unix() { + liberr.ErrIsNil(ctx, gerror.New("验证码错误")) + } + if libUtils.EncryptPassword(req.Code+fmt.Sprint(model.Time.Unix()), user.UserSalt) != model.Password { + liberr.ErrIsNil(ctx, gerror.New("验证码错误")) + } + //账号状态 + if user.UserStatus == 0 { + liberr.ErrIsNil(ctx, gerror.New("账号已被冻结")) + } + }) + return +} + // GetUserByUsername 通过用户名获取用户信息 func (s *userImpl) GetUserByUsername(ctx context.Context, userName string) (user *model2.LoginUserRes, err error) { err = g.Try(ctx, func(ctx context.Context) { @@ -147,6 +173,16 @@ func (s *userImpl) GetUserByMobile(ctx context.Context, mobile string) (user *mo return } +// GetUserByEmail 通过邮箱获取用户信息 +func (s *userImpl) GetUserByEmail(ctx context.Context, email string) (user *model2.LoginUserRes, err error) { + err = g.Try(ctx, func(ctx context.Context) { + user = &model2.LoginUserRes{} + err = dao.SysUser.Ctx(ctx).Fields(user).Where(dao.SysUser.Columns().UserEmail, email).Scan(user) + liberr.ErrIsNil(ctx, err, "验证码错误") + }) + return +} + // GetCodeByMobile 通过手机号获取Code func (s *userImpl) GetCodeByMobile(ctx context.Context, mobile string) (code string, err error) { err = g.Try(ctx, func(ctx context.Context) { @@ -173,6 +209,32 @@ func (s *userImpl) GetCodeByMobile(ctx context.Context, mobile string) (code str return } +// GetCodeByEmail 通过邮箱获取Code +func (s *userImpl) GetCodeByEmail(ctx context.Context, email string) (code string, err error) { + err = g.Try(ctx, func(ctx context.Context) { + user, err := s.GetUserByEmail(ctx, email) + liberr.ErrIsNil(ctx, err) + liberr.ValueIsNil(user, "验证码错误") + log.Printf("GetCodeByMobile >>> email:%s", email) + code = utils.GenerateRandomString(6) + //验证密码 + time := gtime.Now() + password := libUtils.EncryptPassword(code+fmt.Sprint(time.Unix()), user.UserSalt) + codes[email] = CodeModel{ + Code: code, + Time: time, + Password: password, + } + + log.Printf("GetCodeByMobile >>> email:%s", gjson.MustEncodeString(codes[email])) + //账号状态 + if user.UserStatus == 0 { + liberr.ErrIsNil(ctx, gerror.New("账号已被冻结")) + } + }) + return +} + // LoginLog 记录登录日志 func (s *userImpl) LoginLog(ctx context.Context, params *model2.LoginLogParams) { ua := user_agent.New(params.UserAgent)