tg_platform_service.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. package services
  2. import (
  3. "app/commons/config"
  4. "app/commons/core"
  5. "app/commons/core/redisclient"
  6. "app/commons/model/entity"
  7. "bytes"
  8. "context"
  9. "encoding/json"
  10. "errors"
  11. "fmt"
  12. "io"
  13. "net/http"
  14. "time"
  15. )
  16. // TgPlatformService 平台API客户端服务(调用Vitiens平台接口)
  17. type TgPlatformService struct {
  18. CommonService
  19. }
  20. // ---- 请求/响应结构体 ----
  21. // oauthLoginRequest OAuth登录请求
  22. type oauthLoginRequest struct {
  23. Provider string `json:"provider"`
  24. OpenId string `json:"openId"`
  25. Nickname string `json:"nickname"`
  26. Avatar string `json:"avatar"`
  27. InviteCode string `json:"inviteCode,omitempty"`
  28. }
  29. // platformAPIResponse 平台API通用响应
  30. type platformAPIResponse struct {
  31. Code int32 `json:"code"`
  32. Msg string `json:"msg"`
  33. Data json.RawMessage `json:"data"`
  34. Time int64 `json:"time"`
  35. }
  36. // oauthLoginData OAuth登录响应data
  37. type oauthLoginData struct {
  38. Token string `json:"token"`
  39. User oauthUserInfo `json:"user"`
  40. IsNew bool `json:"isNew"`
  41. }
  42. // oauthUserInfo 用户信息
  43. type oauthUserInfo struct {
  44. Id int64 `json:"id"`
  45. Uid string `json:"uid"`
  46. Nickname string `json:"nickname"`
  47. Avatar string `json:"avatar"`
  48. Phone string `json:"phone"`
  49. Email string `json:"email"`
  50. }
  51. // walletInfoData 钱包信息响应data
  52. type walletInfoData struct {
  53. Balance float64 `json:"balance"`
  54. FrozenBalance float64 `json:"frozenBalance"`
  55. TotalRecharge float64 `json:"totalRecharge"`
  56. TotalWithdraw float64 `json:"totalWithdraw"`
  57. TotalTaskIncome float64 `json:"totalTaskIncome"`
  58. }
  59. // ---- 核心方法 ----
  60. // RegisterAndBind 一键注册并绑定
  61. // 1. 检查是否已绑定 → 已绑定直接返回
  62. // 2. 调用Vitiens平台OAuth接口注册/登录
  63. // 3. 在本地创建绑定记录
  64. func (s *TgPlatformService) RegisterAndBind(telegramId int64, username string, firstName string) (userId int64, isNew bool, err error) {
  65. bindService := &TgBindService{}
  66. // 检查是否已绑定
  67. bound, existUserId := bindService.IsUserBound(telegramId, username)
  68. if bound {
  69. return existUserId, false, nil
  70. }
  71. // 调用平台OAuth接口
  72. apiURL := s.getPlatformAPIURL()
  73. if apiURL == "" {
  74. return 0, false, errors.New("平台API地址未配置")
  75. }
  76. // 构造请求
  77. nickname := firstName
  78. if nickname == "" && username != "" {
  79. nickname = username
  80. }
  81. if nickname == "" {
  82. nickname = fmt.Sprintf("TG_%d", telegramId)
  83. }
  84. reqBody := oauthLoginRequest{
  85. Provider: "telegram",
  86. OpenId: fmt.Sprintf("%d", telegramId),
  87. Nickname: nickname,
  88. }
  89. reqBytes, err := json.Marshal(reqBody)
  90. if err != nil {
  91. return 0, false, fmt.Errorf("序列化请求失败: %w", err)
  92. }
  93. // 发起HTTP请求
  94. url := fmt.Sprintf("%s/dt/auth/oauth/login", apiURL)
  95. core.Log.Infof("一键注册 - 调用平台API: %s, telegramId: %d", url, telegramId)
  96. httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(reqBytes))
  97. if err != nil {
  98. return 0, false, fmt.Errorf("创建请求失败: %w", err)
  99. }
  100. httpReq.Header.Set("Content-Type", "application/json")
  101. client := &http.Client{Timeout: 10 * time.Second}
  102. resp, err := client.Do(httpReq)
  103. if err != nil {
  104. return 0, false, fmt.Errorf("请求平台API失败: %w", err)
  105. }
  106. defer resp.Body.Close()
  107. body, err := io.ReadAll(resp.Body)
  108. if err != nil {
  109. return 0, false, fmt.Errorf("读取响应失败: %w", err)
  110. }
  111. core.Log.Infof("一键注册 - 平台API响应: %s", string(body))
  112. // 解析响应
  113. var apiResp platformAPIResponse
  114. if err := json.Unmarshal(body, &apiResp); err != nil {
  115. return 0, false, fmt.Errorf("解析响应失败: %w", err)
  116. }
  117. if apiResp.Code != 200 {
  118. return 0, false, fmt.Errorf("平台注册失败: %s", apiResp.Msg)
  119. }
  120. var loginData oauthLoginData
  121. if err := json.Unmarshal(apiResp.Data, &loginData); err != nil {
  122. return 0, false, fmt.Errorf("解析用户数据失败: %w", err)
  123. }
  124. if loginData.User.Id <= 0 {
  125. return 0, false, errors.New("平台返回的用户ID无效")
  126. }
  127. // 创建本地绑定记录
  128. bind := &entity.TgUserBind{
  129. UserId: loginData.User.Id,
  130. TelegramId: telegramId,
  131. TelegramUsername: username,
  132. TelegramFirstName: firstName,
  133. BindStatus: 1,
  134. BindTime: time.Now(),
  135. }
  136. if err := s.DB().Create(bind).Error; err != nil {
  137. core.Log.Errorf("一键注册 - 创建绑定记录失败: %v", err)
  138. return 0, false, fmt.Errorf("绑定失败: %w", err)
  139. }
  140. // 设置Redis绑定缓存
  141. ctx := context.Background()
  142. rdb := redisclient.DefaultClient()
  143. cacheKey := fmt.Sprintf("tg_bind_cache:%d", telegramId)
  144. rdb.Set(ctx, cacheKey, fmt.Sprintf("%d", loginData.User.Id), 1*time.Hour)
  145. core.Log.Infof("一键注册 - 成功, telegramId: %d, userId: %d, isNew: %v",
  146. telegramId, loginData.User.Id, loginData.IsNew)
  147. return loginData.User.Id, loginData.IsNew, nil
  148. }
  149. // GetUserBalance 查询用户余额(通过平台API)
  150. func (s *TgPlatformService) GetUserBalance(platformToken string) (*walletInfoData, error) {
  151. apiURL := s.getPlatformAPIURL()
  152. if apiURL == "" {
  153. return nil, errors.New("平台API地址未配置")
  154. }
  155. url := fmt.Sprintf("%s/dt/wallet/info", apiURL)
  156. httpReq, err := http.NewRequest("GET", url, nil)
  157. if err != nil {
  158. return nil, fmt.Errorf("创建请求失败: %w", err)
  159. }
  160. httpReq.Header.Set("Authorization", "Bearer "+platformToken)
  161. client := &http.Client{Timeout: 10 * time.Second}
  162. resp, err := client.Do(httpReq)
  163. if err != nil {
  164. return nil, fmt.Errorf("请求失败: %w", err)
  165. }
  166. defer resp.Body.Close()
  167. body, err := io.ReadAll(resp.Body)
  168. if err != nil {
  169. return nil, fmt.Errorf("读取响应失败: %w", err)
  170. }
  171. var apiResp platformAPIResponse
  172. if err := json.Unmarshal(body, &apiResp); err != nil {
  173. return nil, fmt.Errorf("解析响应失败: %w", err)
  174. }
  175. if apiResp.Code != 200 {
  176. return nil, fmt.Errorf("查询失败: %s", apiResp.Msg)
  177. }
  178. var walletData walletInfoData
  179. if err := json.Unmarshal(apiResp.Data, &walletData); err != nil {
  180. return nil, fmt.Errorf("解析钱包数据失败: %w", err)
  181. }
  182. return &walletData, nil
  183. }
  184. // GetUserGrabRecords 查询用户抢红包记录(本地数据库)
  185. // limit <= 0 时返回全部记录
  186. func (s *TgPlatformService) GetUserGrabRecords(telegramId int64, limit int) ([]entity.TgRedPacketRecord, error) {
  187. var records []entity.TgRedPacketRecord
  188. query := s.DB().Where("telegram_id = ?", telegramId).Order("grabbed_at DESC")
  189. if limit > 0 {
  190. query = query.Limit(limit)
  191. }
  192. err := query.Find(&records).Error
  193. if err != nil {
  194. return nil, err
  195. }
  196. return records, nil
  197. }
  198. // getPlatformAPIURL 获取平台API地址
  199. func (s *TgPlatformService) getPlatformAPIURL() string {
  200. telegramConfig := config.EnvConf().Telegram
  201. if telegramConfig != nil && telegramConfig.PlatformAPIURL != "" {
  202. return telegramConfig.PlatformAPIURL
  203. }
  204. return ""
  205. }