mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 16:30:25 +08:00 
			
		
		
		
	
		
			
	
	
		
			120 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			120 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								package api
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								import (
							 | 
						|||
| 
								 | 
							
									"fmt"
							 | 
						|||
| 
								 | 
							
									msgapp "mayfly-go/internal/msg/application"
							 | 
						|||
| 
								 | 
							
									msgentity "mayfly-go/internal/msg/domain/entity"
							 | 
						|||
| 
								 | 
							
									sysapp "mayfly-go/internal/sys/application"
							 | 
						|||
| 
								 | 
							
									sysentity "mayfly-go/internal/sys/domain/entity"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/pkg/biz"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/pkg/cache"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/pkg/otp"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/pkg/req"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/pkg/utils/jsonx"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/pkg/utils/netx"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/pkg/utils/stringx"
							 | 
						|||
| 
								 | 
							
									"mayfly-go/pkg/utils/timex"
							 | 
						|||
| 
								 | 
							
									"time"
							 | 
						|||
| 
								 | 
							
								)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								const (
							 | 
						|||
| 
								 | 
							
									OtpStatusNone  = -1 // 未启用otp校验
							 | 
						|||
| 
								 | 
							
									OtpStatusReg   = 1  // 用户otp secret已注册
							 | 
						|||
| 
								 | 
							
									OtpStatusNoReg = 2  // 用户otp secret未注册
							 | 
						|||
| 
								 | 
							
								)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								// 最后的登录校验(共用)。校验通过返回登录成功响应结果map
							 | 
						|||
| 
								 | 
							
								func LastLoginCheck(account *sysentity.Account, accountLoginSecurity *sysentity.AccountLoginSecurity, loginIp string) map[string]any {
							 | 
						|||
| 
								 | 
							
									biz.IsTrue(account.IsEnable(), "该账号不可用")
							 | 
						|||
| 
								 | 
							
									username := account.Username
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									res := map[string]any{
							 | 
						|||
| 
								 | 
							
										"name":          account.Name,
							 | 
						|||
| 
								 | 
							
										"username":      username,
							 | 
						|||
| 
								 | 
							
										"lastLoginTime": account.LastLoginTime,
							 | 
						|||
| 
								 | 
							
										"lastLoginIp":   account.LastLoginIp,
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									// 默认为不校验otp
							 | 
						|||
| 
								 | 
							
									otpStatus := OtpStatusNone
							 | 
						|||
| 
								 | 
							
									// 访问系统使用的token
							 | 
						|||
| 
								 | 
							
									accessToken := req.CreateToken(account.Id, username)
							 | 
						|||
| 
								 | 
							
									// 若系统配置中设置开启otp双因素校验,则进行otp校验
							 | 
						|||
| 
								 | 
							
									if accountLoginSecurity.UseOtp {
							 | 
						|||
| 
								 | 
							
										otpInfo, otpurl, otpToken := useOtp(account, accountLoginSecurity.OtpIssuer, accessToken)
							 | 
						|||
| 
								 | 
							
										otpStatus = otpInfo.OptStatus
							 | 
						|||
| 
								 | 
							
										if otpurl != "" {
							 | 
						|||
| 
								 | 
							
											res["otpUrl"] = otpurl
							 | 
						|||
| 
								 | 
							
										}
							 | 
						|||
| 
								 | 
							
										accessToken = otpToken
							 | 
						|||
| 
								 | 
							
									} else {
							 | 
						|||
| 
								 | 
							
										// 不进行otp二次校验则直接返回accessToken
							 | 
						|||
| 
								 | 
							
										// 保存登录消息
							 | 
						|||
| 
								 | 
							
										go saveLogin(account, loginIp)
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									// 赋值otp状态
							 | 
						|||
| 
								 | 
							
									res["otp"] = otpStatus
							 | 
						|||
| 
								 | 
							
									res["token"] = accessToken
							 | 
						|||
| 
								 | 
							
									return res
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								func useOtp(account *sysentity.Account, otpIssuer, accessToken string) (*OtpVerifyInfo, string, string) {
							 | 
						|||
| 
								 | 
							
									account.OtpSecretDecrypt()
							 | 
						|||
| 
								 | 
							
									otpSecret := account.OtpSecret
							 | 
						|||
| 
								 | 
							
									// 修改状态为已注册
							 | 
						|||
| 
								 | 
							
									otpStatus := OtpStatusReg
							 | 
						|||
| 
								 | 
							
									otpUrl := ""
							 | 
						|||
| 
								 | 
							
									// 该token用于otp双因素校验
							 | 
						|||
| 
								 | 
							
									token := stringx.Rand(32)
							 | 
						|||
| 
								 | 
							
									// 未注册otp secret或重置了秘钥
							 | 
						|||
| 
								 | 
							
									if otpSecret == "" || otpSecret == "-" {
							 | 
						|||
| 
								 | 
							
										otpStatus = OtpStatusNoReg
							 | 
						|||
| 
								 | 
							
										key, err := otp.NewTOTP(otp.GenerateOpts{
							 | 
						|||
| 
								 | 
							
											AccountName: account.Username,
							 | 
						|||
| 
								 | 
							
											Issuer:      otpIssuer,
							 | 
						|||
| 
								 | 
							
										})
							 | 
						|||
| 
								 | 
							
										biz.ErrIsNilAppendErr(err, "otp生成失败: %s")
							 | 
						|||
| 
								 | 
							
										otpUrl = key.URL()
							 | 
						|||
| 
								 | 
							
										otpSecret = key.Secret()
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
									// 缓存otpInfo, 只有双因素校验通过才可返回真正的accessToken
							 | 
						|||
| 
								 | 
							
									otpInfo := &OtpVerifyInfo{
							 | 
						|||
| 
								 | 
							
										AccountId:   account.Id,
							 | 
						|||
| 
								 | 
							
										Username:    account.Username,
							 | 
						|||
| 
								 | 
							
										OptStatus:   otpStatus,
							 | 
						|||
| 
								 | 
							
										OtpSecret:   otpSecret,
							 | 
						|||
| 
								 | 
							
										AccessToken: accessToken,
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
									cache.SetStr(fmt.Sprintf("otp:token:%s", token), jsonx.ToStr(otpInfo), time.Minute*time.Duration(3))
							 | 
						|||
| 
								 | 
							
									return otpInfo, otpUrl, token
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								// 获取ip与归属地信息
							 | 
						|||
| 
								 | 
							
								func getIpAndRegion(rc *req.Ctx) string {
							 | 
						|||
| 
								 | 
							
									clientIp := rc.GinCtx.ClientIP()
							 | 
						|||
| 
								 | 
							
									return fmt.Sprintf("%s %s", clientIp, netx.Ip2Region(clientIp))
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								// 保存更新账号登录信息
							 | 
						|||
| 
								 | 
							
								func saveLogin(account *sysentity.Account, ip string) {
							 | 
						|||
| 
								 | 
							
									// 更新账号最后登录时间
							 | 
						|||
| 
								 | 
							
									now := time.Now()
							 | 
						|||
| 
								 | 
							
									updateAccount := &sysentity.Account{LastLoginTime: &now}
							 | 
						|||
| 
								 | 
							
									updateAccount.Id = account.Id
							 | 
						|||
| 
								 | 
							
									updateAccount.LastLoginIp = ip
							 | 
						|||
| 
								 | 
							
									// 偷懒为了方便直接获取accountApp
							 | 
						|||
| 
								 | 
							
									sysapp.GetAccountApp().Update(updateAccount)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									// 创建登录消息
							 | 
						|||
| 
								 | 
							
									loginMsg := &msgentity.Msg{
							 | 
						|||
| 
								 | 
							
										RecipientId: int64(account.Id),
							 | 
						|||
| 
								 | 
							
										Msg:         fmt.Sprintf("于[%s]-[%s]登录", ip, timex.DefaultFormat(now)),
							 | 
						|||
| 
								 | 
							
										Type:        1,
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
									loginMsg.CreateTime = &now
							 | 
						|||
| 
								 | 
							
									loginMsg.Creator = account.Username
							 | 
						|||
| 
								 | 
							
									loginMsg.CreatorId = account.Id
							 | 
						|||
| 
								 | 
							
									msgapp.GetMsgApp().Create(loginMsg)
							 | 
						|||
| 
								 | 
							
								}
							 |