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)
|
|
|
|
|
|
}
|