Преглед изворни кода

feat: 一键注册按钮面板、平台OAuth自动绑定、已注册用户隐藏注册按钮

- 新增 TgPlatformService:调用 Vitiens 平台 OAuth API 实现一键注册+自动绑定
- 新增 telegram/buttons.go:按钮面板回调处理(注册/余额/记录/帮助)
- 私聊场景(/start、/help)根据用户绑定状态动态显示按钮,已注册不显示"一键注册"
- 群组欢迎消息保留完整按钮面板(共享消息,回调已处理已绑定提示)
- 更新所有命令文案适配按钮交互,/balance 和 /records 实现实际查询
- 修复 GetUserGrabRecords limit=0 导致 GORM 返回空结果的 bug
- 配置新增 PlatformAPIURL 字段
urban пре 1 недеља
родитељ
комит
f5be22f4f5

+ 5 - 4
magic_server/commons/config/telegram.go

@@ -1,8 +1,9 @@
 package config
 
 type Telegram struct {
-	BotToken   string `mapstructure:"bot-token" json:"botToken" yaml:"bot-token"`       // Bot Token
-	WebhookURL string `mapstructure:"webhook-url" json:"webhookUrl" yaml:"webhook-url"` // Webhook URL (可选)
-	Debug      bool   `mapstructure:"debug" json:"debug" yaml:"debug"`                  // 调试模式
-	BindURL    string `mapstructure:"bind-url" json:"bindUrl" yaml:"bind-url"`          // 绑定页面URL
+	BotToken       string `mapstructure:"bot-token" json:"botToken" yaml:"bot-token"`                      // Bot Token
+	WebhookURL     string `mapstructure:"webhook-url" json:"webhookUrl" yaml:"webhook-url"`                // Webhook URL (可选)
+	Debug          bool   `mapstructure:"debug" json:"debug" yaml:"debug"`                                 // 调试模式
+	BindURL        string `mapstructure:"bind-url" json:"bindUrl" yaml:"bind-url"`                         // 绑定页面URL
+	PlatformAPIURL string `mapstructure:"platform-api-url" json:"platform-api-url" yaml:"platform-api-url"` // Vitiens平台API地址
 }

+ 243 - 0
magic_server/commons/services/tg_platform_service.go

@@ -0,0 +1,243 @@
+package services
+
+import (
+	"app/commons/config"
+	"app/commons/core"
+	"app/commons/core/redisclient"
+	"app/commons/model/entity"
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"time"
+)
+
+// TgPlatformService 平台API客户端服务(调用Vitiens平台接口)
+type TgPlatformService struct {
+	CommonService
+}
+
+// ---- 请求/响应结构体 ----
+
+// oauthLoginRequest OAuth登录请求
+type oauthLoginRequest struct {
+	Provider   string `json:"provider"`
+	OpenId     string `json:"openId"`
+	Nickname   string `json:"nickname"`
+	Avatar     string `json:"avatar"`
+	InviteCode string `json:"inviteCode,omitempty"`
+}
+
+// platformAPIResponse 平台API通用响应
+type platformAPIResponse struct {
+	Code int32           `json:"code"`
+	Msg  string          `json:"msg"`
+	Data json.RawMessage `json:"data"`
+	Time int64           `json:"time"`
+}
+
+// oauthLoginData OAuth登录响应data
+type oauthLoginData struct {
+	Token string        `json:"token"`
+	User  oauthUserInfo `json:"user"`
+	IsNew bool          `json:"isNew"`
+}
+
+// oauthUserInfo 用户信息
+type oauthUserInfo struct {
+	Id       int64  `json:"id"`
+	Uid      string `json:"uid"`
+	Nickname string `json:"nickname"`
+	Avatar   string `json:"avatar"`
+	Phone    string `json:"phone"`
+	Email    string `json:"email"`
+}
+
+// walletInfoData 钱包信息响应data
+type walletInfoData struct {
+	Balance         float64 `json:"balance"`
+	FrozenBalance   float64 `json:"frozenBalance"`
+	TotalRecharge   float64 `json:"totalRecharge"`
+	TotalWithdraw   float64 `json:"totalWithdraw"`
+	TotalTaskIncome float64 `json:"totalTaskIncome"`
+}
+
+// ---- 核心方法 ----
+
+// RegisterAndBind 一键注册并绑定
+// 1. 检查是否已绑定 → 已绑定直接返回
+// 2. 调用Vitiens平台OAuth接口注册/登录
+// 3. 在本地创建绑定记录
+func (s *TgPlatformService) RegisterAndBind(telegramId int64, username string, firstName string) (userId int64, isNew bool, err error) {
+	bindService := &TgBindService{}
+
+	// 检查是否已绑定
+	bound, existUserId := bindService.IsUserBound(telegramId, username)
+	if bound {
+		return existUserId, false, nil
+	}
+
+	// 调用平台OAuth接口
+	apiURL := s.getPlatformAPIURL()
+	if apiURL == "" {
+		return 0, false, errors.New("平台API地址未配置")
+	}
+
+	// 构造请求
+	nickname := firstName
+	if nickname == "" && username != "" {
+		nickname = username
+	}
+	if nickname == "" {
+		nickname = fmt.Sprintf("TG_%d", telegramId)
+	}
+
+	reqBody := oauthLoginRequest{
+		Provider: "telegram",
+		OpenId:   fmt.Sprintf("%d", telegramId),
+		Nickname: nickname,
+	}
+
+	reqBytes, err := json.Marshal(reqBody)
+	if err != nil {
+		return 0, false, fmt.Errorf("序列化请求失败: %w", err)
+	}
+
+	// 发起HTTP请求
+	url := fmt.Sprintf("%s/dt/auth/oauth/login", apiURL)
+	core.Log.Infof("一键注册 - 调用平台API: %s, telegramId: %d", url, telegramId)
+
+	httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(reqBytes))
+	if err != nil {
+		return 0, false, fmt.Errorf("创建请求失败: %w", err)
+	}
+	httpReq.Header.Set("Content-Type", "application/json")
+
+	client := &http.Client{Timeout: 10 * time.Second}
+	resp, err := client.Do(httpReq)
+	if err != nil {
+		return 0, false, fmt.Errorf("请求平台API失败: %w", err)
+	}
+	defer resp.Body.Close()
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return 0, false, fmt.Errorf("读取响应失败: %w", err)
+	}
+
+	core.Log.Infof("一键注册 - 平台API响应: %s", string(body))
+
+	// 解析响应
+	var apiResp platformAPIResponse
+	if err := json.Unmarshal(body, &apiResp); err != nil {
+		return 0, false, fmt.Errorf("解析响应失败: %w", err)
+	}
+
+	if apiResp.Code != 200 {
+		return 0, false, fmt.Errorf("平台注册失败: %s", apiResp.Msg)
+	}
+
+	var loginData oauthLoginData
+	if err := json.Unmarshal(apiResp.Data, &loginData); err != nil {
+		return 0, false, fmt.Errorf("解析用户数据失败: %w", err)
+	}
+
+	if loginData.User.Id <= 0 {
+		return 0, false, errors.New("平台返回的用户ID无效")
+	}
+
+	// 创建本地绑定记录
+	bind := &entity.TgUserBind{
+		UserId:            loginData.User.Id,
+		TelegramId:        telegramId,
+		TelegramUsername:  username,
+		TelegramFirstName: firstName,
+		BindStatus:        1,
+		BindTime:          time.Now(),
+	}
+	if err := s.DB().Create(bind).Error; err != nil {
+		core.Log.Errorf("一键注册 - 创建绑定记录失败: %v", err)
+		return 0, false, fmt.Errorf("绑定失败: %w", err)
+	}
+
+	// 设置Redis绑定缓存
+	ctx := context.Background()
+	rdb := redisclient.DefaultClient()
+	cacheKey := fmt.Sprintf("tg_bind_cache:%d", telegramId)
+	rdb.Set(ctx, cacheKey, fmt.Sprintf("%d", loginData.User.Id), 1*time.Hour)
+
+	core.Log.Infof("一键注册 - 成功, telegramId: %d, userId: %d, isNew: %v",
+		telegramId, loginData.User.Id, loginData.IsNew)
+
+	return loginData.User.Id, loginData.IsNew, nil
+}
+
+// GetUserBalance 查询用户余额(通过平台API)
+func (s *TgPlatformService) GetUserBalance(platformToken string) (*walletInfoData, error) {
+	apiURL := s.getPlatformAPIURL()
+	if apiURL == "" {
+		return nil, errors.New("平台API地址未配置")
+	}
+
+	url := fmt.Sprintf("%s/dt/wallet/info", apiURL)
+	httpReq, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, fmt.Errorf("创建请求失败: %w", err)
+	}
+	httpReq.Header.Set("Authorization", "Bearer "+platformToken)
+
+	client := &http.Client{Timeout: 10 * time.Second}
+	resp, err := client.Do(httpReq)
+	if err != nil {
+		return nil, fmt.Errorf("请求失败: %w", err)
+	}
+	defer resp.Body.Close()
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("读取响应失败: %w", err)
+	}
+
+	var apiResp platformAPIResponse
+	if err := json.Unmarshal(body, &apiResp); err != nil {
+		return nil, fmt.Errorf("解析响应失败: %w", err)
+	}
+
+	if apiResp.Code != 200 {
+		return nil, fmt.Errorf("查询失败: %s", apiResp.Msg)
+	}
+
+	var walletData walletInfoData
+	if err := json.Unmarshal(apiResp.Data, &walletData); err != nil {
+		return nil, fmt.Errorf("解析钱包数据失败: %w", err)
+	}
+
+	return &walletData, nil
+}
+
+// GetUserGrabRecords 查询用户抢红包记录(本地数据库)
+// limit <= 0 时返回全部记录
+func (s *TgPlatformService) GetUserGrabRecords(telegramId int64, limit int) ([]entity.TgRedPacketRecord, error) {
+	var records []entity.TgRedPacketRecord
+	query := s.DB().Where("telegram_id = ?", telegramId).Order("grabbed_at DESC")
+	if limit > 0 {
+		query = query.Limit(limit)
+	}
+	err := query.Find(&records).Error
+	if err != nil {
+		return nil, err
+	}
+	return records, nil
+}
+
+// getPlatformAPIURL 获取平台API地址
+func (s *TgPlatformService) getPlatformAPIURL() string {
+	telegramConfig := config.EnvConf().Telegram
+	if telegramConfig != nil && telegramConfig.PlatformAPIURL != "" {
+		return telegramConfig.PlatformAPIURL
+	}
+	return ""
+}

+ 205 - 0
magic_server/telegram/buttons.go

@@ -0,0 +1,205 @@
+package telegram
+
+import (
+	"app/commons/core"
+	"app/commons/services"
+	"fmt"
+	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+	"strings"
+)
+
+// handleActionCallback 处理按钮面板的回调(action_ 前缀)
+func handleActionCallback(callback *tgbotapi.CallbackQuery) {
+	action := strings.TrimPrefix(callback.Data, "action_")
+
+	switch action {
+	case "register":
+		handleRegisterAction(callback)
+	case "balance":
+		handleBalanceAction(callback)
+	case "records":
+		handleRecordsAction(callback)
+	case "help":
+		handleHelpAction(callback)
+	default:
+		answerCallback(callback.ID, "未知操作")
+	}
+}
+
+// handleRegisterAction 一键注册+绑定
+func handleRegisterAction(callback *tgbotapi.CallbackQuery) {
+	telegramId := callback.From.ID
+	username := callback.From.UserName
+	firstName := callback.From.FirstName
+
+	platformService := &services.TgPlatformService{}
+
+	userId, isNew, err := platformService.RegisterAndBind(telegramId, username, firstName)
+	if err != nil {
+		answerCallback(callback.ID, fmt.Sprintf("⚠️ %s", err.Error()))
+		core.Log.Errorf("一键注册失败 - telegramId: %d, error: %v", telegramId, err)
+		return
+	}
+
+	if isNew {
+		answerCallback(callback.ID, "✅ 注册成功!已自动绑定,可以抢红包了!")
+		// 私信发送详细信息
+		text := fmt.Sprintf("🎉 注册成功!\n\n"+
+			"✅ 平台账户已创建并自动绑定\n"+
+			"🆔 用户ID: %d\n\n"+
+			"现在可以直接在群里抢红包了!", userId)
+		sendTextMessage(telegramId, text)
+	} else {
+		// 用户已存在(之前通过OAuth注册但未在bot绑定),现在已自动绑定
+		answerCallback(callback.ID, "✅ 已绑定!可以抢红包了!")
+	}
+
+	core.Log.Infof("一键注册 - 用户: %s, telegramId: %d, userId: %d, isNew: %v",
+		username, telegramId, userId, isNew)
+}
+
+// handleBalanceAction 查询余额
+func handleBalanceAction(callback *tgbotapi.CallbackQuery) {
+	telegramId := callback.From.ID
+	username := callback.From.UserName
+
+	// 检查绑定状态
+	bindService := &services.TgBindService{}
+	bound, _ := bindService.IsUserBound(telegramId, username)
+	if !bound {
+		answerCallback(callback.ID, "⚠️ 请先点击「一键注册」绑定账户")
+		return
+	}
+
+	// 查询本地抢红包总额
+	platformService := &services.TgPlatformService{}
+	records, err := platformService.GetUserGrabRecords(telegramId, 0)
+	if err != nil {
+		answerCallback(callback.ID, "⚠️ 查询失败,请稍后再试")
+		return
+	}
+
+	// 统计抢红包总额和次数
+	var totalAmount float64
+	for _, r := range records {
+		totalAmount += r.Amount.InexactFloat64()
+	}
+
+	text := fmt.Sprintf("💰 账户信息\n\n"+
+		"🧧 抢红包次数: %d 次\n"+
+		"💵 抢红包总额: %.2f\n\n"+
+		"💡 详细余额请登录平台查看",
+		len(records), totalAmount)
+
+	sendTextMessage(telegramId, text)
+	answerCallback(callback.ID, "💰 已发送到私聊")
+}
+
+// handleRecordsAction 查询抢红包记录
+func handleRecordsAction(callback *tgbotapi.CallbackQuery) {
+	telegramId := callback.From.ID
+	username := callback.From.UserName
+
+	// 检查绑定状态
+	bindService := &services.TgBindService{}
+	bound, _ := bindService.IsUserBound(telegramId, username)
+	if !bound {
+		answerCallback(callback.ID, "⚠️ 请先点击「一键注册」绑定账户")
+		return
+	}
+
+	// 查询最近10条记录
+	platformService := &services.TgPlatformService{}
+	records, err := platformService.GetUserGrabRecords(telegramId, 10)
+	if err != nil {
+		answerCallback(callback.ID, "⚠️ 查询失败,请稍后再试")
+		return
+	}
+
+	if len(records) == 0 {
+		sendTextMessage(telegramId, "📊 暂无抢红包记录\n\n等待群内红包发放后即可参与!")
+		answerCallback(callback.ID, "📊 已发送到私聊")
+		return
+	}
+
+	text := "📊 最近抢红包记录\n\n"
+	for i, r := range records {
+		timeStr := r.GrabbedAt.Format("01-02 15:04")
+		bestTag := ""
+		if r.IsBest == 1 {
+			bestTag = " 🏆"
+		}
+		text += fmt.Sprintf("%d. %s | %.2f%s\n", i+1, timeStr, r.Amount.InexactFloat64(), bestTag)
+	}
+	text += "\n💡 🏆 = 手气最佳"
+
+	sendTextMessage(telegramId, text)
+	answerCallback(callback.ID, "📊 已发送到私聊")
+}
+
+// handleHelpAction 帮助信息
+func handleHelpAction(callback *tgbotapi.CallbackQuery) {
+	helpText := `📖 红包机器人帮助
+
+🧧 红包相关:
+• 群组定期自动发放红包
+• 点击红包消息下方的按钮即可抢红包
+• 普通红包:每人金额相同
+• 手气红包:每人金额随机
+
+📝 使用步骤:
+1️⃣ 点击「📝 一键注册」自动注册并绑定
+2️⃣ 等待群内红包发放
+3️⃣ 点击红包按钮抢红包
+
+💡 按钮功能:
+• 📝 一键注册 - 自动注册平台账户并绑定
+• 💰 余额查询 - 查看抢红包统计
+• 📊 抢红包记录 - 查看最近领取记录
+• ❓ 帮助 - 显示此帮助信息
+
+⚠️ 注意:
+• 未注册无法抢红包
+• 每个红包只能抢一次
+• 红包有效期由管理员设定`
+
+	sendTextMessage(callback.From.ID, helpText)
+	answerCallback(callback.ID, "📖 已发送到私聊")
+}
+
+// buildWelcomeKeyboard 构建欢迎消息按钮面板(群组消息用,含注册按钮)
+func buildWelcomeKeyboard() tgbotapi.InlineKeyboardMarkup {
+	return tgbotapi.NewInlineKeyboardMarkup(
+		tgbotapi.NewInlineKeyboardRow(
+			tgbotapi.NewInlineKeyboardButtonData("📝 一键注册", "action_register"),
+			tgbotapi.NewInlineKeyboardButtonData("💰 余额查询", "action_balance"),
+		),
+		tgbotapi.NewInlineKeyboardRow(
+			tgbotapi.NewInlineKeyboardButtonData("📊 抢红包记录", "action_records"),
+			tgbotapi.NewInlineKeyboardButtonData("❓ 帮助", "action_help"),
+		),
+	)
+}
+
+// buildKeyboardForUser 根据用户绑定状态构建按钮面板(私聊用)
+// 已注册用户不显示"一键注册"按钮
+func buildKeyboardForUser(telegramId int64, username string) tgbotapi.InlineKeyboardMarkup {
+	bindService := &services.TgBindService{}
+	bound, _ := bindService.IsUserBound(telegramId, username)
+
+	if bound {
+		// 已注册:只显示功能按钮
+		return tgbotapi.NewInlineKeyboardMarkup(
+			tgbotapi.NewInlineKeyboardRow(
+				tgbotapi.NewInlineKeyboardButtonData("💰 余额查询", "action_balance"),
+				tgbotapi.NewInlineKeyboardButtonData("📊 抢红包记录", "action_records"),
+			),
+			tgbotapi.NewInlineKeyboardRow(
+				tgbotapi.NewInlineKeyboardButtonData("❓ 帮助", "action_help"),
+			),
+		)
+	}
+
+	// 未注册:显示完整面板(含注册按钮)
+	return buildWelcomeKeyboard()
+}

+ 129 - 39
magic_server/telegram/commands.go

@@ -33,54 +33,83 @@ func handleCommand(msg *tgbotapi.Message) {
 	}
 }
 
-// handleStartCommand 处理 /start 命令
+// handleStartCommand 处理 /start 命令(私聊时带按钮面板)
 func handleStartCommand(msg *tgbotapi.Message) {
-	welcomeText := `🎉 欢迎使用红包机器人!
-
-🧧 功能介绍:
-• 群组定期发放红包福利
-• 支持普通红包(固定金额)
-• 支持手气红包(随机金额)
-
-📝 使用步骤:
-1️⃣ 前往平台注册账户
-2️⃣ 使用 /bind 绑定平台账户
-3️⃣ 等待群内红包发放
-4️⃣ 点击按钮抢红包
+	telegramId := msg.From.ID
+	username := msg.From.UserName
 
-💡 快速开始:
-/help - 查看帮助
-/bind - 绑定账户
+	// 检查用户是否已注册
+	bindService := &services.TgBindService{}
+	bound, _ := bindService.IsUserBound(telegramId, username)
 
-⚠️ 未绑定账户无法抢红包`
+	var welcomeText string
+	if bound {
+		welcomeText = "🎉 欢迎回来!\n\n" +
+			"✅ 您已注册,可以直接抢红包\n\n" +
+			"🧧 功能介绍:\n" +
+			"• 群组定期发放红包福利\n" +
+			"• 支持普通红包和手气红包\n\n" +
+			"👇 点击下方按钮使用功能:"
+	} else {
+		welcomeText = "🎉 欢迎使用红包机器人!\n\n" +
+			"🧧 功能介绍:\n" +
+			"• 群组定期发放红包福利\n" +
+			"• 支持普通红包(固定金额)\n" +
+			"• 支持手气红包(随机金额)\n\n" +
+			"📝 使用步骤:\n" +
+			"1️⃣ 点击「📝 一键注册」自动注册并绑定\n" +
+			"2️⃣ 等待群内红包发放\n" +
+			"3️⃣ 点击按钮抢红包\n\n" +
+			"👇 点击下方按钮开始:"
+	}
 
-	sendTextMessage(msg.Chat.ID, welcomeText)
+	keyboard := buildKeyboardForUser(telegramId, username)
+	sendMessageWithKeyboard(msg.Chat.ID, welcomeText, keyboard)
 }
 
 // handleHelpCommand 处理 /help 命令
 func handleHelpCommand(msg *tgbotapi.Message) {
-	helpText := `📖 命令帮助
-
-🧧 红包相关:
-• 群组定期自动发放红包
-• 点击红包消息下方的按钮即可抢红包
-• 普通红包:每人金额相同
-• 手气红包:每人金额随机
-
-👤 账户相关:
-/bind - 绑定平台账户
+	telegramId := msg.From.ID
+	username := msg.From.UserName
 
-ℹ️ 其他:
-/help - 显示此帮助
-/start - 显示欢迎消息
-/groupinfo - 查看群组信息(仅限群组)
+	// 检查用户是否已注册
+	bindService := &services.TgBindService{}
+	bound, _ := bindService.IsUserBound(telegramId, username)
 
-💡 提示:
-• 抢红包需要先绑定账户
-• 每个红包只能抢一次
-• 红包有效期由管理员设定`
+	var helpText string
+	if bound {
+		helpText = "📖 红包机器人帮助\n\n" +
+			"✅ 您已注册,可以直接抢红包\n\n" +
+			"🧧 红包玩法:\n" +
+			"• 群组定期自动发放红包\n" +
+			"• 点击红包消息下方按钮即可抢\n" +
+			"• 普通红包:每人金额相同\n" +
+			"• 手气红包:每人金额随机\n\n" +
+			"💡 按钮功能:\n" +
+			"• 💰 余额查询 - 查看抢红包统计\n" +
+			"• 📊 抢红包记录 - 查看领取明细\n" +
+			"• ❓ 帮助 - 显示此帮助\n\n" +
+			"⚠️ 注意:每个红包只能抢一次"
+	} else {
+		helpText = "📖 红包机器人帮助\n\n" +
+			"🧧 红包玩法:\n" +
+			"• 群组定期自动发放红包\n" +
+			"• 点击红包消息下方按钮即可抢\n" +
+			"• 普通红包:每人金额相同\n" +
+			"• 手气红包:每人金额随机\n\n" +
+			"📝 快速开始:\n" +
+			"1️⃣ 点击「📝 一键注册」自动注册并绑定\n" +
+			"2️⃣ 等待群内红包,点击抢!\n\n" +
+			"💡 按钮功能:\n" +
+			"• 📝 一键注册 - 自动注册平台账户\n" +
+			"• 💰 余额查询 - 查看抢红包统计\n" +
+			"• 📊 抢红包记录 - 查看领取明细\n" +
+			"• ❓ 帮助 - 显示此帮助\n\n" +
+			"⚠️ 注意:未注册无法抢红包,每个红包只能抢一次"
+	}
 
-	sendTextMessage(msg.Chat.ID, helpText)
+	keyboard := buildKeyboardForUser(telegramId, username)
+	sendMessageWithKeyboard(msg.Chat.ID, helpText, keyboard)
 }
 
 // handleBindCommand 处理 /bind 命令
@@ -138,12 +167,73 @@ func handleBindCommand(msg *tgbotapi.Message) {
 
 // handleBalanceCommand 处理 /balance 命令
 func handleBalanceCommand(msg *tgbotapi.Message) {
-	sendTextMessage(msg.Chat.ID, "💰 余额查询功能开发中...")
+	telegramId := msg.From.ID
+	username := msg.From.UserName
+
+	bindService := &services.TgBindService{}
+	bound, _ := bindService.IsUserBound(telegramId, username)
+	if !bound {
+		sendTextMessage(msg.Chat.ID, "⚠️ 请先点击「📝 一键注册」绑定账户")
+		return
+	}
+
+	platformService := &services.TgPlatformService{}
+	records, err := platformService.GetUserGrabRecords(telegramId, 0)
+	if err != nil {
+		sendTextMessage(msg.Chat.ID, "⚠️ 查询失败,请稍后再试")
+		return
+	}
+
+	var totalAmount float64
+	for _, r := range records {
+		totalAmount += r.Amount.InexactFloat64()
+	}
+
+	text := fmt.Sprintf("💰 账户信息\n\n"+
+		"🧧 抢红包次数: %d 次\n"+
+		"💵 抢红包总额: %.2f\n\n"+
+		"💡 详细余额请登录平台查看",
+		len(records), totalAmount)
+
+	sendTextMessage(msg.Chat.ID, text)
 }
 
 // handleRecordsCommand 处理 /records 命令
 func handleRecordsCommand(msg *tgbotapi.Message) {
-	sendTextMessage(msg.Chat.ID, "📊 记录查询功能开发中...")
+	telegramId := msg.From.ID
+	username := msg.From.UserName
+
+	bindService := &services.TgBindService{}
+	bound, _ := bindService.IsUserBound(telegramId, username)
+	if !bound {
+		sendTextMessage(msg.Chat.ID, "⚠️ 请先点击「📝 一键注册」绑定账户")
+		return
+	}
+
+	platformService := &services.TgPlatformService{}
+	records, err := platformService.GetUserGrabRecords(telegramId, 10)
+	if err != nil {
+		sendTextMessage(msg.Chat.ID, "⚠️ 查询失败,请稍后再试")
+		return
+	}
+
+	if len(records) == 0 {
+		sendTextMessage(msg.Chat.ID, "📊 暂无抢红包记录\n\n等待群内红包发放后即可参与!")
+		return
+	}
+
+	text := "📊 最近抢红包记录\n\n"
+	for i, r := range records {
+		timeStr := r.GrabbedAt.Format("01-02 15:04")
+		bestTag := ""
+		if r.IsBest == 1 {
+			bestTag = " 🏆"
+		}
+		text += fmt.Sprintf("%d. %s | %.2f%s\n", i+1, timeStr, r.Amount.InexactFloat64(), bestTag)
+	}
+	text += "\n💡 🏆 = 手气最佳"
+
+	sendTextMessage(msg.Chat.ID, text)
 }
 
 // handleGroupInfoCommand 处理 /groupinfo 命令

+ 15 - 29
magic_server/telegram/messages.go

@@ -114,45 +114,31 @@ func answerCallback(callbackID string, text string) {
 	}
 }
 
-// sendGroupWelcome 发送机器人加入群组欢迎消息
+// sendGroupWelcome 发送机器人加入群组欢迎消息(带按钮面板)
 func sendGroupWelcome(chatID int64) {
-	text := `👋 大家好!我是红包机器人
-
-🎉 功能介绍:
-• 定期发放群组红包福利
-• 支持普通红包和手气红包
-
-📝 使用方法:
-1. 前往平台注册账户
-2. 私聊我发送 /bind 绑定账户
-3. 绑定后即可抢群内红包
-
-⚠️ 未绑定账户无法抢红包
-
-💡 使用 /help 查看详细帮助`
-
-	sendTextMessage(chatID, text)
+	text := "👋 大家好!我是红包机器人\n\n" +
+		"🎉 功能介绍:\n" +
+		"• 定期发放群组红包福利\n" +
+		"• 支持普通红包和手气红包\n\n" +
+		"📝 点击「一键注册」即可参与抢红包:"
+
+	keyboard := buildWelcomeKeyboard()
+	sendMessageWithKeyboard(chatID, text, keyboard)
 }
 
-// sendNewMemberWelcome 发送新成员欢迎消息
+// sendNewMemberWelcome 发送新成员欢迎消息(带按钮面板)
 func sendNewMemberWelcome(chatID int64, username, firstName string) {
 	displayName := firstName
 	if username != "" {
 		displayName = "@" + username
 	}
 
-	text := fmt.Sprintf(`👋 欢迎 %s 加入群组!
-
-🧧 本群定期发放红包福利!
-
-📝 快速开始:
-1. 前往平台注册账户
-2. 私聊我发送 /bind 绑定账户
-3. 绑定后即可抢红包
+	text := fmt.Sprintf("👋 欢迎 %s 加入群组!\n\n"+
+		"🧧 本群定期发放红包福利!\n\n"+
+		"📝 点击下方「一键注册」即可参与抢红包:", displayName)
 
-⚠️ 未绑定账户无法抢红包`, displayName)
-
-	sendTextMessage(chatID, text)
+	keyboard := buildWelcomeKeyboard()
+	sendMessageWithKeyboard(chatID, text, keyboard)
 }
 
 // SendRedPacketToGroup 发送红包消息到群组(供外部调用),返回 Telegram 消息ID

+ 6 - 0
magic_server/telegram/redpacket.go

@@ -13,6 +13,12 @@ import (
 func handleCallbackQuery(callback *tgbotapi.CallbackQuery) {
 	data := callback.Data
 
+	// 处理按钮面板回调(一键注册、余额查询、记录、帮助)
+	if strings.HasPrefix(data, "action_") {
+		handleActionCallback(callback)
+		return
+	}
+
 	// 处理抢红包
 	if strings.HasPrefix(data, "grab_") {
 		packetNo := strings.TrimPrefix(data, "grab_")