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)
 | 
							pb.RegisterLoginSessionServiceServer(server, instance)
 | 
				
			||||||
		this.rest(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)
 | 
							var instance = this.serviceInstance(&services.UserAccessKeyService{}).(*services.UserAccessKeyService)
 | 
				
			||||||
		pb.RegisterUserAccessKeyServiceServer(server, instance)
 | 
							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": []
 | 
					      "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",
 | 
					      "name": "edgeLogins",
 | 
				
			||||||
      "engine": "InnoDB",
 | 
					      "engine": "InnoDB",
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user