| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- package services
- import (
- "app/commons/core/redisclient"
- "app/commons/model/entity"
- "context"
- "crypto/rand"
- "encoding/json"
- "errors"
- "fmt"
- "math/big"
- "time"
- "gorm.io/gorm"
- )
- type TgBindService struct {
- CommonService
- }
- const (
- redisKeyBindToken = "tg_bind_token:%s" // 绑定令牌
- redisKeyBindLimit = "tg_bind_limit:%d" // 生成限频
- redisKeyBindCache = "tg_bind_cache:%d" // 绑定缓存
- bindTokenExpire = 5 * time.Minute // 令牌有效期
- bindLimitExpire = 60 * time.Second // 限频间隔
- bindCacheExpire = 1 * time.Hour // 缓存有效期
- )
- type bindTokenData struct {
- TelegramId int64 `json:"telegramId"`
- TelegramUsername string `json:"telegramUsername"`
- }
- // GenerateBindToken 生成绑定令牌
- func (s *TgBindService) GenerateBindToken(telegramId int64, telegramUsername string) (string, error) {
- ctx := context.Background()
- rdb := redisclient.DefaultClient()
- // 限频检查
- limitKey := fmt.Sprintf(redisKeyBindLimit, telegramId)
- if rdb.Exists(ctx, limitKey).Val() > 0 {
- return "", errors.New("请稍后再试(60秒内只能生成一次)")
- }
- // 生成6位码
- token := s.randomAlphanumeric(6)
- // 存 Redis
- tokenKey := fmt.Sprintf(redisKeyBindToken, token)
- data := bindTokenData{
- TelegramId: telegramId,
- TelegramUsername: telegramUsername,
- }
- jsonBytes, _ := json.Marshal(data)
- rdb.Set(ctx, tokenKey, string(jsonBytes), bindTokenExpire)
- // 存 DB
- bindToken := &entity.TgBindToken{
- Token: token,
- TelegramId: telegramId,
- TelegramUsername: telegramUsername,
- Status: 0,
- ExpireAt: time.Now().Add(bindTokenExpire),
- }
- s.DB().Create(bindToken)
- // 设置限频
- rdb.Set(ctx, limitKey, "1", bindLimitExpire)
- return token, nil
- }
- // BindTelegramUser 使用令牌绑定 Telegram 用户到平台账户
- func (s *TgBindService) BindTelegramUser(userId int64, token string) (*entity.TgUserBind, error) {
- ctx := context.Background()
- rdb := redisclient.DefaultClient()
- // 从 Redis 查令牌
- tokenKey := fmt.Sprintf(redisKeyBindToken, token)
- val, err := rdb.Get(ctx, tokenKey).Result()
- var data bindTokenData
- if err == nil {
- json.Unmarshal([]byte(val), &data)
- } else {
- // Redis 没有,查 DB
- var dbToken entity.TgBindToken
- if err := s.DB().Where("token = ? AND status = 0", token).First(&dbToken).Error; err != nil {
- return nil, errors.New("绑定码无效或已过期")
- }
- if time.Now().After(dbToken.ExpireAt) {
- return nil, errors.New("绑定码已过期")
- }
- data.TelegramId = dbToken.TelegramId
- data.TelegramUsername = dbToken.TelegramUsername
- }
- // 检查 telegramId 是否已绑定其他用户
- var existBind entity.TgUserBind
- err = s.DB().Where("telegram_id = ? AND bind_status = 1", data.TelegramId).First(&existBind).Error
- if err == nil && existBind.UserId != userId {
- return nil, errors.New("该Telegram账户已绑定其他平台账户")
- }
- if err == nil && existBind.UserId == userId {
- // 已绑定同一用户,直接返回
- return &existBind, nil
- }
- // 检查 userId 是否已绑定其他 telegramId
- err = s.DB().Where("user_id = ? AND bind_status = 1", userId).First(&existBind).Error
- if err == nil && existBind.TelegramId != data.TelegramId {
- return nil, errors.New("该平台账户已绑定其他Telegram账户")
- }
- // 创建绑定记录
- bind := &entity.TgUserBind{
- UserId: userId,
- TelegramId: data.TelegramId,
- TelegramUsername: data.TelegramUsername,
- TelegramFirstName: "",
- BindStatus: 1,
- BindTime: time.Now(),
- }
- if err := s.DB().Create(bind).Error; err != nil {
- return nil, fmt.Errorf("绑定失败: %w", err)
- }
- // 更新令牌状态
- s.DB().Model(&entity.TgBindToken{}).Where("token = ?", token).Updates(map[string]interface{}{
- "status": 1,
- "user_id": userId,
- })
- // 删除 Redis 令牌,设置绑定缓存
- rdb.Del(ctx, tokenKey)
- cacheKey := fmt.Sprintf(redisKeyBindCache, data.TelegramId)
- rdb.Set(ctx, cacheKey, fmt.Sprintf("%d", userId), bindCacheExpire)
- return bind, nil
- }
- // IsUserBound 检查 telegramId 是否已绑定,返回 (是否绑定, userId)
- func (s *TgBindService) IsUserBound(telegramId int64) (bool, int64) {
- ctx := context.Background()
- rdb := redisclient.DefaultClient()
- // 查 Redis 缓存
- cacheKey := fmt.Sprintf(redisKeyBindCache, telegramId)
- val, err := rdb.Get(ctx, cacheKey).Result()
- if err == nil && val != "" {
- var userId int64
- fmt.Sscanf(val, "%d", &userId)
- if userId > 0 {
- return true, userId
- }
- }
- // 查 DB
- var bind entity.TgUserBind
- err = s.DB().Where("telegram_id = ? AND bind_status = 1", telegramId).First(&bind).Error
- if err != nil {
- if errors.Is(err, gorm.ErrRecordNotFound) {
- return false, 0
- }
- return false, 0
- }
- // 写入缓存
- rdb.Set(ctx, cacheKey, fmt.Sprintf("%d", bind.UserId), bindCacheExpire)
- return true, bind.UserId
- }
- // GetBindByUserId 根据平台 userId 查询绑定记录
- func (s *TgBindService) GetBindByUserId(userId int64) (*entity.TgUserBind, error) {
- var bind entity.TgUserBind
- if err := s.DB().Where("user_id = ? AND bind_status = 1", userId).First(&bind).Error; err != nil {
- return nil, err
- }
- return &bind, nil
- }
- // randomAlphanumeric 生成 n 位大写字母+数字随机码
- func (s *TgBindService) randomAlphanumeric(n int) string {
- const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
- result := make([]byte, n)
- for i := range result {
- num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
- result[i] = charset[num.Int64()]
- }
- return string(result)
- }
|