Files
TrangleAgent/Backend/internal/logic/login/login.go

296 lines
8.7 KiB
Go
Raw Normal View History

2026-01-18 18:20:40 +08:00
// Package login internal/logic/login/login.go
package login
import (
"context"
"database/sql"
"errors"
"fmt"
2026-01-18 19:07:41 +08:00
v1 "TrangleAgent/api/login/v1"
"TrangleAgent/internal/consts"
"TrangleAgent/internal/dao"
"TrangleAgent/internal/middleware"
"TrangleAgent/internal/model"
"TrangleAgent/internal/service"
2026-01-18 18:20:40 +08:00
"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)
}