Files
TrangleAgent/Backend/internal/logic/login/login.go
2026-01-18 19:07:41 +08:00

297 lines
8.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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{}
}
// 关键!!在 init 里完成注册
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)
}