mirror of
				https://github.com/TeaOSLab/EdgeAPI.git
				synced 2025-11-04 16:00:24 +08:00 
			
		
		
		
	实现Ticket登录
This commit is contained in:
		
							
								
								
									
										121
									
								
								internal/db/models/login_ticket_dao.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								internal/db/models/login_ticket_dao.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAPI/internal/goman"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
 | 
			
		||||
	_ "github.com/go-sql-driver/mysql"
 | 
			
		||||
	"github.com/iwind/TeaGo/Tea"
 | 
			
		||||
	"github.com/iwind/TeaGo/dbs"
 | 
			
		||||
	"github.com/iwind/TeaGo/rands"
 | 
			
		||||
	"github.com/iwind/TeaGo/types"
 | 
			
		||||
	stringutil "github.com/iwind/TeaGo/utils/string"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	if !teaconst.IsMain {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 清理过期的票据
 | 
			
		||||
	var ticker = time.NewTicker(time.Duration(rands.Int(36, 48)) * time.Hour)
 | 
			
		||||
	goman.New(func() {
 | 
			
		||||
		for range ticker.C {
 | 
			
		||||
			err := SharedLoginTicketDAO.CleanExpiredTickets(nil)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				remotelogs.Error("LoginTicketDAO", "clean expired tickets failed: "+err.Error())
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LoginTicketDAO dbs.DAO
 | 
			
		||||
 | 
			
		||||
func NewLoginTicketDAO() *LoginTicketDAO {
 | 
			
		||||
	return dbs.NewDAO(&LoginTicketDAO{
 | 
			
		||||
		DAOObject: dbs.DAOObject{
 | 
			
		||||
			DB:     Tea.Env,
 | 
			
		||||
			Table:  "edgeLoginTickets",
 | 
			
		||||
			Model:  new(LoginTicket),
 | 
			
		||||
			PkName: "id",
 | 
			
		||||
		},
 | 
			
		||||
	}).(*LoginTicketDAO)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var SharedLoginTicketDAO *LoginTicketDAO
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	dbs.OnReady(func() {
 | 
			
		||||
		SharedLoginTicketDAO = NewLoginTicketDAO()
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateLoginTicket 创建票据
 | 
			
		||||
func (this *LoginTicketDAO) CreateLoginTicket(tx *dbs.Tx, adminId int64, userId int64, ip string) (ticketValue string, err error) {
 | 
			
		||||
	if adminId <= 0 && userId <= 0 {
 | 
			
		||||
		err = errors.New("either 'adminId' or 'userId' must be greater than 0")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(ip) > 0 && !iputils.IsValid(ip) {
 | 
			
		||||
		err = errors.New("invalid ip: '" + ip + "'")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ticketValue = stringutil.Md5(types.String(adminId) + "@" + types.String(userId) + types.String(time.Now().UnixNano()) + "@" + types.String(rand.Int63()) + "@" + ip)
 | 
			
		||||
 | 
			
		||||
	var op = NewLoginTicketOperator()
 | 
			
		||||
	op.AdminId = adminId
 | 
			
		||||
	op.UserId = userId
 | 
			
		||||
	op.ExpiresAt = time.Now().Unix() + 600 /* 10 minutes */
 | 
			
		||||
	op.Ip = ip
 | 
			
		||||
	op.Value = ticketValue
 | 
			
		||||
	err = this.Save(tx, op)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ticketValue, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindLoginTicketWithValue 查找票据
 | 
			
		||||
func (this *LoginTicketDAO) FindLoginTicketWithValue(tx *dbs.Tx, value string) (*LoginTicket, error) {
 | 
			
		||||
	if len(value) == 0 {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(value) != 32 {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	one, err := this.Query(tx).
 | 
			
		||||
		Attr("value", value).
 | 
			
		||||
		Gt("expiresAt", time.Now().Unix()).
 | 
			
		||||
		Find()
 | 
			
		||||
	if one == nil || err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var ticket = one.(*LoginTicket)
 | 
			
		||||
 | 
			
		||||
	// delete the ticket
 | 
			
		||||
	err = this.Query(tx).
 | 
			
		||||
		Pk(ticket.Id).
 | 
			
		||||
		DeleteQuickly()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ticket, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CleanExpiredTickets 清理过期的票据
 | 
			
		||||
func (this *LoginTicketDAO) CleanExpiredTickets(tx *dbs.Tx) error {
 | 
			
		||||
	return this.Query(tx).
 | 
			
		||||
		Lt("expiresAt", time.Now().Unix()).
 | 
			
		||||
		DeleteQuickly()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								internal/db/models/login_ticket_dao_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								internal/db/models/login_ticket_dao_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
package models_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	_ "github.com/go-sql-driver/mysql"
 | 
			
		||||
	_ "github.com/iwind/TeaGo/bootstrap"
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										35
									
								
								internal/db/models/login_ticket_model.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								internal/db/models/login_ticket_model.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import "github.com/iwind/TeaGo/dbs"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	LoginTicketField_Id        dbs.FieldName = "id"        // ID
 | 
			
		||||
	LoginTicketField_ExpiresAt dbs.FieldName = "expiresAt" // 过期时间
 | 
			
		||||
	LoginTicketField_Value     dbs.FieldName = "value"     // 票据值
 | 
			
		||||
	LoginTicketField_AdminId   dbs.FieldName = "adminId"   // 管理员ID
 | 
			
		||||
	LoginTicketField_UserId    dbs.FieldName = "userId"    // 用户ID
 | 
			
		||||
	LoginTicketField_Ip        dbs.FieldName = "ip"        // 用户IP
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LoginTicket 登录票据
 | 
			
		||||
type LoginTicket struct {
 | 
			
		||||
	Id        uint64 `field:"id"`        // ID
 | 
			
		||||
	ExpiresAt uint64 `field:"expiresAt"` // 过期时间
 | 
			
		||||
	Value     string `field:"value"`     // 票据值
 | 
			
		||||
	AdminId   uint32 `field:"adminId"`   // 管理员ID
 | 
			
		||||
	UserId    uint32 `field:"userId"`    // 用户ID
 | 
			
		||||
	Ip        string `field:"ip"`        // 用户IP
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LoginTicketOperator struct {
 | 
			
		||||
	Id        any // ID
 | 
			
		||||
	ExpiresAt any // 过期时间
 | 
			
		||||
	Value     any // 票据值
 | 
			
		||||
	AdminId   any // 管理员ID
 | 
			
		||||
	UserId    any // 用户ID
 | 
			
		||||
	Ip        any // 用户IP
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewLoginTicketOperator() *LoginTicketOperator {
 | 
			
		||||
	return &LoginTicketOperator{}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								internal/db/models/login_ticket_model_ext.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								internal/db/models/login_ticket_model_ext.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
package models
 | 
			
		||||
@@ -334,6 +334,11 @@ func (this *APINode) registerServices(server *grpc.Server) {
 | 
			
		||||
		pb.RegisterLoginSessionServiceServer(server, instance)
 | 
			
		||||
		this.rest(instance)
 | 
			
		||||
	}
 | 
			
		||||
	{
 | 
			
		||||
		var instance = this.serviceInstance(&services.LoginTicketService{}).(*services.LoginTicketService)
 | 
			
		||||
		pb.RegisterLoginTicketServiceServer(server, instance)
 | 
			
		||||
		this.rest(instance)
 | 
			
		||||
	}
 | 
			
		||||
	{
 | 
			
		||||
		var instance = this.serviceInstance(&services.UserAccessKeyService{}).(*services.UserAccessKeyService)
 | 
			
		||||
		pb.RegisterUserAccessKeyServiceServer(server, instance)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										71
									
								
								internal/rpc/services/service_login_ticket.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								internal/rpc/services/service_login_ticket.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
 | 
			
		||||
 | 
			
		||||
package services
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAPI/internal/db/models"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LoginTicketService 登录票据相关服务
 | 
			
		||||
type LoginTicketService struct {
 | 
			
		||||
	BaseService
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateLoginTicket 创建票据
 | 
			
		||||
func (this *LoginTicketService) CreateLoginTicket(ctx context.Context, req *pb.CreateLoginTicketRequest) (*pb.CreateLoginTicketResponse, error) {
 | 
			
		||||
	_, err := this.ValidateAdmin(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if req.AdminId <= 0 && req.UserId <= 0 {
 | 
			
		||||
		return nil, errors.New("either 'adminId' or 'userId' must be greater than 0")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(req.Ip) > 0 && !iputils.IsValid(req.Ip) {
 | 
			
		||||
		return nil, errors.New("invalid ip: '" + req.Ip + "'")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tx = this.NullTx()
 | 
			
		||||
	value, err := models.SharedLoginTicketDAO.CreateLoginTicket(tx, req.AdminId, req.UserId, req.Ip)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &pb.CreateLoginTicketResponse{Value: value}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindLoginTicketWithValue 查找票据
 | 
			
		||||
// 查找成功后,会自动删除票据信息,所以票据信息只能查询一次
 | 
			
		||||
func (this *LoginTicketService) FindLoginTicketWithValue(ctx context.Context, req *pb.FindLoginTicketWithValueRequest) (*pb.FindLoginTicketWithValueResponse, error) {
 | 
			
		||||
	_, _, err := this.ValidateAdminAndUser(ctx, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tx = this.NullTx()
 | 
			
		||||
	ticket, err := models.SharedLoginTicketDAO.FindLoginTicketWithValue(tx, req.Value)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ticket == nil {
 | 
			
		||||
		return &pb.FindLoginTicketWithValueResponse{
 | 
			
		||||
			LoginTicket: nil,
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &pb.FindLoginTicketWithValueResponse{
 | 
			
		||||
		LoginTicket: &pb.LoginTicket{
 | 
			
		||||
			Id:        int64(ticket.Id),
 | 
			
		||||
			ExpiresAt: int64(ticket.ExpiresAt),
 | 
			
		||||
			Value:     ticket.Value,
 | 
			
		||||
			AdminId:   int64(ticket.AdminId),
 | 
			
		||||
			UserId:    int64(ticket.UserId),
 | 
			
		||||
			Ip:        ticket.Ip,
 | 
			
		||||
		},
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -115193,6 +115193,53 @@
 | 
			
		||||
      ],
 | 
			
		||||
      "records": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "name": "edgeLoginTickets",
 | 
			
		||||
      "engine": "InnoDB",
 | 
			
		||||
      "charset": "utf8mb4_general_ci",
 | 
			
		||||
      "definition": "CREATE TABLE `edgeLoginTickets` (\n  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',\n  `expiresAt` bigint(11) unsigned DEFAULT '0' COMMENT '过期时间',\n  `value` varchar(32) DEFAULT NULL COMMENT '票据值',\n  `adminId` int(11) unsigned DEFAULT '0' COMMENT '管理员ID',\n  `userId` int(11) unsigned DEFAULT '0' COMMENT '用户ID',\n  `ip` varchar(64) DEFAULT NULL COMMENT '用户IP',\n  PRIMARY KEY (`id`),\n  KEY `value` (`value`),\n  KEY `expiresAt` (`expiresAt`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='登录票据'",
 | 
			
		||||
      "fields": [
 | 
			
		||||
        {
 | 
			
		||||
          "name": "id",
 | 
			
		||||
          "definition": "bigint(20) unsigned auto_increment COMMENT 'ID'"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "name": "expiresAt",
 | 
			
		||||
          "definition": "bigint(11) unsigned DEFAULT '0' COMMENT '过期时间'"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "name": "value",
 | 
			
		||||
          "definition": "varchar(32) COMMENT '票据值'"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "name": "adminId",
 | 
			
		||||
          "definition": "int(11) unsigned DEFAULT '0' COMMENT '管理员ID'"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "name": "userId",
 | 
			
		||||
          "definition": "int(11) unsigned DEFAULT '0' COMMENT '用户ID'"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "name": "ip",
 | 
			
		||||
          "definition": "varchar(64) COMMENT '用户IP'"
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      "indexes": [
 | 
			
		||||
        {
 | 
			
		||||
          "name": "PRIMARY",
 | 
			
		||||
          "definition": "UNIQUE KEY `PRIMARY` (`id`) USING BTREE"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "name": "value",
 | 
			
		||||
          "definition": "KEY `value` (`value`) USING BTREE"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "name": "expiresAt",
 | 
			
		||||
          "definition": "KEY `expiresAt` (`expiresAt`) USING BTREE"
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      "records": []
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "name": "edgeLogins",
 | 
			
		||||
      "engine": "InnoDB",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user