feat: 实现前端卡密管理界面
- 卡密列表展示与分页功能 - 单个/批量创建卡密 - 卡密删除与批量删除 - 卡密导出功能 (file-saver) - 启用/禁用状态切换 - 状态判断 (有效/已使用/已失效) - Toast 通知系统 (vue-sonner) - 登录页面错误提示优化 - 后端登录错误消息中文化
This commit is contained in:
229
backend/internal/service/chatgpt_service.go
Normal file
229
backend/internal/service/chatgpt_service.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ChatGPTService ChatGPT API 服务
|
||||
type ChatGPTService struct {
|
||||
client *http.Client
|
||||
baseURL string
|
||||
}
|
||||
|
||||
// NewChatGPTService 创建 ChatGPT 服务
|
||||
func NewChatGPTService() *ChatGPTService {
|
||||
return &ChatGPTService{
|
||||
client: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
baseURL: "https://chatgpt.com/backend-api",
|
||||
}
|
||||
}
|
||||
|
||||
// SubscriptionInfo 订阅信息
|
||||
type SubscriptionInfo struct {
|
||||
SeatsInUse int `json:"seats_in_use"`
|
||||
SeatsEntitled int `json:"seats_entitled"`
|
||||
ActiveStart time.Time `json:"active_start"`
|
||||
ActiveUntil time.Time `json:"active_until"`
|
||||
IsValid bool `json:"is_valid"`
|
||||
}
|
||||
|
||||
// subscriptionResponse ChatGPT API 响应结构
|
||||
type subscriptionResponse struct {
|
||||
SeatsInUse int `json:"seats_in_use"`
|
||||
SeatsEntitled int `json:"seats_entitled"`
|
||||
ActiveStart string `json:"active_start"`
|
||||
ActiveUntil string `json:"active_until"`
|
||||
}
|
||||
|
||||
// GetSubscription 获取订阅信息
|
||||
func (s *ChatGPTService) GetSubscription(teamAccountID, authToken string) (*SubscriptionInfo, error) {
|
||||
url := fmt.Sprintf("%s/subscriptions?account_id=%s", s.baseURL, teamAccountID)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Authorization", authToken)
|
||||
req.Header.Set("chatgpt-account-id", teamAccountID)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
// 检查响应状态码
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return &SubscriptionInfo{IsValid: false}, nil
|
||||
}
|
||||
|
||||
var subResp subscriptionResponse
|
||||
if err := json.Unmarshal(body, &subResp); err != nil {
|
||||
return &SubscriptionInfo{IsValid: false}, nil
|
||||
}
|
||||
|
||||
// 解析时间
|
||||
info := &SubscriptionInfo{
|
||||
SeatsInUse: subResp.SeatsInUse,
|
||||
SeatsEntitled: subResp.SeatsEntitled,
|
||||
IsValid: true,
|
||||
}
|
||||
|
||||
if subResp.ActiveStart != "" {
|
||||
if t, err := time.Parse(time.RFC3339, subResp.ActiveStart); err == nil {
|
||||
info.ActiveStart = t
|
||||
}
|
||||
}
|
||||
if subResp.ActiveUntil != "" {
|
||||
if t, err := time.Parse(time.RFC3339, subResp.ActiveUntil); err == nil {
|
||||
info.ActiveUntil = t
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// InviteRequest 邀请请求结构
|
||||
type InviteRequest struct {
|
||||
EmailAddresses []string `json:"email_addresses"`
|
||||
Role string `json:"role"`
|
||||
ResendEmails bool `json:"resend_emails"`
|
||||
}
|
||||
|
||||
// InviteResponse 邀请响应
|
||||
type InviteResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
RawBody string `json:"-"`
|
||||
}
|
||||
|
||||
// SendInvite 发送邀请到 ChatGPT Team
|
||||
func (s *ChatGPTService) SendInvite(teamAccountID, authToken, email string) (*InviteResponse, error) {
|
||||
url := fmt.Sprintf("%s/accounts/%s/invites", s.baseURL, teamAccountID)
|
||||
|
||||
// 构建请求体
|
||||
reqBody := InviteRequest{
|
||||
EmailAddresses: []string{email},
|
||||
Role: "standard-user",
|
||||
ResendEmails: true,
|
||||
}
|
||||
|
||||
bodyBytes, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Authorization", authToken)
|
||||
req.Header.Set("chatgpt-account-id", teamAccountID)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
// 检查响应状态码
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
return &InviteResponse{
|
||||
Success: true,
|
||||
Message: "Invitation sent successfully",
|
||||
RawBody: string(body),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 解析错误响应
|
||||
return &InviteResponse{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("API returned status %d: %s", resp.StatusCode, string(body)),
|
||||
RawBody: string(body),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RemoveMemberResponse 移除成员响应
|
||||
type RemoveMemberResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message,omitempty"`
|
||||
RawBody string `json:"-"`
|
||||
}
|
||||
|
||||
// RemoveMember 从 ChatGPT Team 移除成员
|
||||
func (s *ChatGPTService) RemoveMember(teamAccountID, authToken, email string) (*RemoveMemberResponse, error) {
|
||||
url := fmt.Sprintf("%s/accounts/%s/invites", s.baseURL, teamAccountID)
|
||||
|
||||
// 构建请求体 - 只需要邮箱
|
||||
reqBody := map[string]string{
|
||||
"email_address": email,
|
||||
}
|
||||
|
||||
bodyBytes, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", url, bytes.NewBuffer(bodyBytes))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Authorization", authToken)
|
||||
req.Header.Set("chatgpt-account-id", teamAccountID)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
// 检查响应状态码
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
return &RemoveMemberResponse{
|
||||
Success: true,
|
||||
Message: "Member removed successfully",
|
||||
RawBody: string(body),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 解析错误响应
|
||||
return &RemoveMemberResponse{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("API returned status %d: %s", resp.StatusCode, string(body)),
|
||||
RawBody: string(body),
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user