// Package login internal/logic/login/login.go package login import ( "context" "database/sql" "errors" "fmt" v1 "TrangleAgent/api/login/v1" "TrangleAgent/internal/consts" "TrangleAgent/internal/dao" "TrangleAgent/internal/middleware" "TrangleAgent/internal/model" "TrangleAgent/internal/service" "time" "github.com/gogf/gf/v2/crypto/gmd5" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/glog" "github.com/gogf/gf/v2/util/grand" "github.com/golang-jwt/jwt/v5" ) // 注意:名字改成 sLogin,首字母小写 s + 大写 Login type sLogin struct{} func New() *sLogin { return &sLogin{} } func init() { service.RegisterLogin(New()) } func (s *sLogin) Register(ctx context.Context, loginReq *v1.RegisterReq) (res *v1.RegisterRes, err error) { // 判断参数是否正常 账户的长度要在5-10 密码要经过MD5加密 if len(loginReq.Account) < 5 || len(loginReq.Account) > 10 { glog.Debugf(ctx, "账户长度校验失败,长度:%d", len(loginReq.Account)) return nil, errors.New("账户长度要在5-10之间") } if len(loginReq.Password) < 6 || len(loginReq.Password) > 32 { glog.Debugf(ctx, "密码长度校验失败,长度:%d", len(loginReq.Password)) return nil, errors.New("密码长度要在6-32之间") } password, err := gmd5.Encrypt(loginReq.Password + consts.Salt) if err != nil { glog.Errorf(ctx, "密码加密失败:%v", err) return nil, gerror.Wrap(err, "密码加密失败,请稍后重试!") } loginData := model.LoginField{ Account: loginReq.Account, Password: password, Nickname: "特工007", } _, err = dao.Users.Ctx(ctx).Data(loginData).Insert() if err != nil { glog.Errorf(ctx, "插入账号到数据库失败:%v", err) return nil, gerror.Wrap(err, "插入账号到数,据库失败,请稍后重试!") } return } func (s *sLogin) Login(ctx context.Context, loginReq *v1.LoginReq) (res *v1.LoginRes, err error) { //校验参数 if len(loginReq.Account) < 5 || len(loginReq.Account) > 10 { glog.Debugf(ctx, "账户长度校验失败,长度:%d", len(loginReq.Account)) return nil, errors.New("账户长度要在5-10之间") } if len(loginReq.Password) < 6 || len(loginReq.Password) > 32 { glog.Debugf(ctx, "密码长度校验失败,长度:%d", len(loginReq.Password)) return nil, errors.New("密码长度要在6-32之间") } // 2. 先根据账号查数据库 var user model.LoginField err = dao.Users.Ctx(ctx).Where("account", loginReq.Account).Scan(&user) if err != nil { glog.Errorf(ctx, "查询用户失败:%v", err) return nil, gerror.Wrap(err, "登录失败,请稍后重试!") } if user.Account == "" { // 没查到用户 return nil, gerror.NewCode(gcode.CodeInvalidParameter, "账号或密码错误") } // 3. 使用"相同规则"加密用户输入的密码,并和 DB 中的密码对比 // 注意:这里的加密规则必须和 Register 时一致 encryptedInput, err := gmd5.Encrypt(loginReq.Password + consts.Salt) if err != nil { glog.Errorf(ctx, "密码加密失败:%v", err) return nil, gerror.Wrap(err, "密码加密失败,请稍后重试!") } if user.Password != encryptedInput { return nil, gerror.NewCode(gcode.CodeInvalidParameter, "账号或密码错误") } // 5. 种 Session(如果你确实需要 session) if err := setSession(ctx, user.Account); err != nil { glog.Errorf(ctx, "设置Session失败:%v", err) return nil, gerror.Wrap(err, "设置登录状态失败,请稍后重试!") } //JWT 生成 // Create claims with user information claims := &middleware.JWTClaims{ Username: loginReq.Account, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), }, } // 生成Token token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) signedToken, err := token.SignedString([]byte(middleware.JwtSecretKey)) if err != nil { return nil, gerror.NewCode(gcode.CodeInternalError, "Failed to generate token") } res = &v1.LoginRes{ Token: signedToken, Account: user.Account, } return } // 通过邮箱注册 func (s *sLogin) RegisterByEmail(ctx context.Context, req *v1.RegisterByEmailReq) (res *v1.RegisterByEmailRes, err error) { // 先验证验证码 emailService := service.NewEmailService() if !emailService.VerifyVerificationCode(req.Email, req.Code) { return nil, gerror.NewCode(gcode.CodeInvalidParameter, "验证码错误或已过期") } // 检查邮箱是否已存在 count, err := dao.Users.Ctx(ctx).Where("email", req.Email).Count() if err != nil { return nil, gerror.Wrap(err, "查询用户失败") } if count > 0 { return nil, gerror.NewCode(gcode.CodeInvalidParameter, "该邮箱已被注册") } // 生成随机账号和密码 account := fmt.Sprintf("user_%s", grand.S(6)) password := grand.S(10) // 加密密码 encryptedPassword, err := gmd5.Encrypt(password + consts.Salt) if err != nil { return nil, gerror.Wrap(err, "密码加密失败") } // 创建用户 userData := model.LoginField{ Account: account, Password: encryptedPassword, Nickname: "特工007", Email: req.Email, } _, err = dao.Users.Ctx(ctx).Data(userData).Insert() if err != nil { return nil, gerror.Wrap(err, "注册失败") } res = &v1.RegisterByEmailRes{ Email: req.Email, } return } // 通过邮箱登录 func (s *sLogin) LoginByEmail(ctx context.Context, req *v1.LoginByEmailReq) (res *v1.LoginByEmailRes, err error) { emailService := service.NewEmailService() // 验证验证码 if !emailService.VerifyVerificationCode(req.Email, req.Code) { return nil, gerror.NewCode(gcode.CodeInvalidParameter, "验证码错误或已过期") } // 根据邮箱查找用户 var user model.LoginField err = dao.Users.Ctx(ctx).Where("email", req.Email).Scan(&user) if err != nil && err != sql.ErrNoRows { return nil, gerror.WrapCode(gcode.CodeInternalError, err, "查询用户失败") } // 如果用户不存在,则创建新用户 if user.Email == "" { account := fmt.Sprintf("user_%s", grand.S(6)) password := grand.S(10) // 加密密码 encryptedPassword, err := gmd5.Encrypt(password + consts.Salt) if err != nil { return nil, gerror.Wrap(err, "密码加密失败") } // 创建用户 userData := model.LoginField{ Account: account, Password: encryptedPassword, Nickname: "特工007", Email: req.Email, } _, err = dao.Users.Ctx(ctx).Data(userData).Insert() if err != nil { return nil, gerror.Wrap(err, "创建用户失败") } user = userData } // 设置session if err := setSession(ctx, user.Account); err != nil { glog.Errorf(ctx, "设置Session失败:%v", err) return nil, gerror.Wrap(err, "设置登录状态失败,请稍后重试!") } // 生成JWT Token claims := &middleware.JWTClaims{ Username: user.Account, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) signedToken, err := token.SignedString([]byte(middleware.JwtSecretKey)) if err != nil { return nil, gerror.NewCode(gcode.CodeInternalError, "Failed to generate token") } res = &v1.LoginByEmailRes{ Token: signedToken, Email: req.Email, } return } // 发送验证码 func (s *sLogin) SendVerificationCode(ctx context.Context, req *v1.SendVerificationCodeReq) (res *v1.SendVerificationCodeRes, err error) { emailService := service.NewEmailService() err = emailService.SendVerificationCode(ctx, req.Email) if err != nil { return nil, gerror.Wrap(err, "发送验证码失败") } res = &v1.SendVerificationCodeRes{ Success: true, } return } // 退出登录 func (s *sLogin) Logout(ctx context.Context, req *v1.LogoutReq) (res *v1.LogoutRes, err error) { // 从上下文里拿到当前请求 r := g.RequestFromCtx(ctx) if r == nil { return nil, gerror.New("无效的请求上下文") } // 1. 删除当前登录用到的 Session 信息 // 和 Login / setSession 里保持一致:都是用 "userAccount" 这个 key if err = r.Session.Remove("userAccount"); err != nil { glog.Errorf(ctx, "删除Session失败:%v", err) return nil, gerror.Wrap(err, "退出登录失败,请稍后重试!") } // 如果你想彻底清空这个 Session(不只删一个字段),可以用: // if err = r.Session.RemoveAll(); err != nil { ... } // 2. 构造返回结果(一般是一个空结构就够了) res = &v1.LogoutRes{} return res, nil } // 给前端种session func setSession(ctx context.Context, account string) error { return g.RequestFromCtx(ctx).Session.Set("userAccount", account) }