|
@@ -16,45 +16,106 @@ type CdnService interface {
|
|
|
BindPlan(ctx context.Context, req v1.Plan) (int64, error)
|
|
|
RenewPlan(ctx context.Context, req v1.RenewalPlan) error
|
|
|
CreateWebsite(ctx context.Context, req v1.Website) (int64, error)
|
|
|
- EditProtocol(ctx context.Context, req v1.ProxyJson,action string) error
|
|
|
+ EditProtocol(ctx context.Context, req v1.ProxyJson, action string) error
|
|
|
CreateOrigin(ctx context.Context, req v1.Origin) (int64, error)
|
|
|
EditOrigin(ctx context.Context, req v1.Origin) error
|
|
|
}
|
|
|
+
|
|
|
func NewCdnService(
|
|
|
- service *Service,
|
|
|
+ service *Service,
|
|
|
conf *viper.Viper,
|
|
|
request RequestService,
|
|
|
cdnRepository repository.CdnRepository,
|
|
|
) CdnService {
|
|
|
return &cdnService{
|
|
|
- Service: service,
|
|
|
- Url: conf.GetString("flexCdn.Url"),
|
|
|
- AccessKeyID: conf.GetString("flexCdn.AccessKeyID"),
|
|
|
- AccessKeySecret: conf.GetString("flexCdn.AccessKeySecret"),
|
|
|
- request: request,
|
|
|
- cdnRepository: cdnRepository,
|
|
|
+ Service: service,
|
|
|
+ Url: conf.GetString("flexCdn.Url"),
|
|
|
+ AccessKeyID: conf.GetString("flexCdn.AccessKeyID"),
|
|
|
+ AccessKeySecret: conf.GetString("flexCdn.AccessKeySecret"),
|
|
|
+ request: request,
|
|
|
+ cdnRepository: cdnRepository,
|
|
|
+ maxRetryCount: 3, // 可以配置最大重试次数
|
|
|
+ retryDelaySeconds: 2, // 可以配置重试间隔
|
|
|
}
|
|
|
}
|
|
|
|
|
|
type cdnService struct {
|
|
|
*Service
|
|
|
- Url string
|
|
|
- AccessKeyID string
|
|
|
- AccessKeySecret string
|
|
|
- request RequestService
|
|
|
- cdnRepository repository.CdnRepository
|
|
|
+ Url string
|
|
|
+ AccessKeyID string
|
|
|
+ AccessKeySecret string
|
|
|
+ request RequestService
|
|
|
+ cdnRepository repository.CdnRepository
|
|
|
+ maxRetryCount int
|
|
|
+ retryDelaySeconds int
|
|
|
}
|
|
|
|
|
|
-func (s *cdnService) SendData(ctx context.Context, formData map[string]interface{}, apiUrl string,) ([]byte, error) {
|
|
|
- token, err := s.Toekn(ctx)
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
+// SendData 是一个通用的请求发送方法,它封装了 token 过期重试的逻辑
|
|
|
+func (s *cdnService) sendDataWithTokenRetry(ctx context.Context, formData map[string]interface{}, apiUrl string) ([]byte, error) {
|
|
|
+ var resBody []byte
|
|
|
+
|
|
|
+ for i := 0; i < s.maxRetryCount; i++ {
|
|
|
+ token, err := s.Token(ctx) // 确保使用最新的 token
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("获取或刷新 token 失败: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ resBody, err = s.request.Request(ctx, formData, apiUrl, "X-Cloud-Access-Token", token)
|
|
|
+ if err != nil {
|
|
|
+ // 检查错误是否是由于 token 无效引起的
|
|
|
+ if s.isTokenInvalidError(resBody, err) { // 判断是否是 token 无效错误
|
|
|
+ _, getTokenErr := s.GetToken(ctx)
|
|
|
+ if getTokenErr != nil {
|
|
|
+ return nil, fmt.Errorf("刷新 token 失败: %w", getTokenErr)
|
|
|
+ }
|
|
|
+ continue // 继续下一次循环,使用新的 token
|
|
|
+ }
|
|
|
+ return nil, fmt.Errorf("请求失败: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 成功获取到响应,处理响应体
|
|
|
+ var generalResponse v1.GeneralResponse[any]
|
|
|
+ if err := json.Unmarshal(resBody, &generalResponse); err != nil {
|
|
|
+ return nil, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查 API 返回的 code 和 message
|
|
|
+ if generalResponse.Code == 400 && generalResponse.Message == "invalid access token" {
|
|
|
+ fmt.Printf("尝试 %d/%d:API 返回无效 token 错误,准备刷新并重试...\n", i+1, s.maxRetryCount)
|
|
|
+ _, getTokenErr := s.GetToken(ctx)
|
|
|
+ if getTokenErr != nil {
|
|
|
+ return nil, fmt.Errorf("刷新 token 失败: %w", getTokenErr)
|
|
|
+ }
|
|
|
+ continue // 继续下一次循环,使用新的 token
|
|
|
+ }
|
|
|
+
|
|
|
+ // 成功处理,返回结果
|
|
|
+ return resBody, nil
|
|
|
}
|
|
|
- resBody, err := s.request.Request(ctx, formData, apiUrl, "X-Cloud-Access-Token", token)
|
|
|
+
|
|
|
+ // 如果循环结束仍未成功,则返回最终错误
|
|
|
+ return nil, fmt.Errorf("达到最大重试次数后请求仍然失败")
|
|
|
+}
|
|
|
+
|
|
|
+// isTokenInvalidError 是一个辅助函数,用于判断错误是否是由于 token 无效引起的。
|
|
|
+// 你需要根据你的 request.Request 实现来具体实现这个函数。
|
|
|
+// 例如,你可以检查 resBody 是否包含特定的错误信息。
|
|
|
+func (s *cdnService) isTokenInvalidError(resBody []byte, err error) bool {
|
|
|
+ // 示例:如果请求本身就返回了非 200 的错误,并且响应体中有特定信息
|
|
|
if err != nil {
|
|
|
- return nil, err
|
|
|
+ // 尝试从 resBody 中解析出错误信息,判断是否是 token 无效
|
|
|
+ var generalResponse v1.GeneralResponse[any]
|
|
|
+ if parseErr := json.Unmarshal(resBody, &generalResponse); parseErr == nil {
|
|
|
+ if generalResponse.Code == 400 && generalResponse.Message == "invalid access token" {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 或者检查 err 本身是否有相关的错误信息
|
|
|
+ // if strings.Contains(err.Error(), "invalid access token") {
|
|
|
+ // return true
|
|
|
+ // }
|
|
|
}
|
|
|
- return resBody, nil
|
|
|
+ return false
|
|
|
}
|
|
|
|
|
|
func (s *cdnService) GetToken(ctx context.Context) (string, error) {
|
|
@@ -64,6 +125,7 @@ func (s *cdnService) GetToken(ctx context.Context) (string, error) {
|
|
|
"accessKey": s.AccessKeySecret,
|
|
|
}
|
|
|
apiUrl := s.Url + "APIAccessTokenService/getAPIAccessToken"
|
|
|
+
|
|
|
resBody, err := s.request.Request(ctx, formData, apiUrl, "X-Cloud-Access-Token", "")
|
|
|
if err != nil {
|
|
|
return "", err
|
|
@@ -85,7 +147,7 @@ func (s *cdnService) GetToken(ctx context.Context) (string, error) {
|
|
|
return res.Data.Token, nil
|
|
|
}
|
|
|
|
|
|
-func (s *cdnService) Toekn(ctx context.Context) (string, error) {
|
|
|
+func (s *cdnService) Token(ctx context.Context) (string, error) {
|
|
|
token, err := s.cdnRepository.GetToken(ctx)
|
|
|
if err != nil {
|
|
|
return "", err
|
|
@@ -104,7 +166,7 @@ func (s *cdnService) AddUser(ctx context.Context, req v1.User) (int64, error) {
|
|
|
formData := map[string]interface{}{
|
|
|
"id": req.ID,
|
|
|
"username": req.Username,
|
|
|
- "password": "a7fKiKujgAzzsJ6",
|
|
|
+ "password": "a7fKiKujgAzzsJ6", // 这个密码应该被妥善管理,而不是硬编码
|
|
|
"fullname": req.Fullname,
|
|
|
"mobile": req.Mobile,
|
|
|
"tel": req.Tel,
|
|
@@ -114,7 +176,7 @@ func (s *cdnService) AddUser(ctx context.Context, req v1.User) (int64, error) {
|
|
|
"nodeClusterId": 1,
|
|
|
}
|
|
|
apiUrl := s.Url + "UserService/createUser"
|
|
|
- resBody, err := s.SendData(ctx, formData, apiUrl)
|
|
|
+ resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl)
|
|
|
if err != nil {
|
|
|
return 0, err
|
|
|
}
|
|
@@ -123,7 +185,7 @@ func (s *cdnService) AddUser(ctx context.Context, req v1.User) (int64, error) {
|
|
|
}
|
|
|
var res v1.GeneralResponse[DataStr]
|
|
|
if err := json.Unmarshal(resBody, &res); err != nil {
|
|
|
- return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
|
|
|
+ return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
|
|
|
}
|
|
|
|
|
|
if res.Code != 200 {
|
|
@@ -141,7 +203,7 @@ func (s *cdnService) CreateGroup(ctx context.Context, req v1.Group) (int64, erro
|
|
|
"name": req.Name,
|
|
|
}
|
|
|
apiUrl := s.Url + "ServerGroupService/createServerGroup"
|
|
|
- resBody, err := s.SendData(ctx, formData, apiUrl)
|
|
|
+ resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
|
|
|
if err != nil {
|
|
|
return 0, err
|
|
|
}
|
|
@@ -150,7 +212,7 @@ func (s *cdnService) CreateGroup(ctx context.Context, req v1.Group) (int64, erro
|
|
|
}
|
|
|
var res v1.GeneralResponse[DataStr]
|
|
|
if err := json.Unmarshal(resBody, &res); err != nil {
|
|
|
- return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
|
|
|
+ return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
|
|
|
}
|
|
|
if res.Code != 200 {
|
|
|
return 0, fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Message)
|
|
@@ -161,21 +223,20 @@ func (s *cdnService) CreateGroup(ctx context.Context, req v1.Group) (int64, erro
|
|
|
return res.Data.ServerGroupId, nil
|
|
|
}
|
|
|
|
|
|
-
|
|
|
//分配套餐
|
|
|
func (s *cdnService) BindPlan(ctx context.Context, req v1.Plan) (int64, error) {
|
|
|
formData := map[string]interface{}{
|
|
|
- "userId": req.UserId,
|
|
|
- "planId": req.PlanId,
|
|
|
- "dayTo": req.DayTo,
|
|
|
- "period": req.Period,
|
|
|
- "countPeriod": req.CountPeriod,
|
|
|
- "name": req.Name,
|
|
|
- "isFree": req.IsFree,
|
|
|
- "periodDayTo": req.PeriodDayTo,
|
|
|
+ "userId": req.UserId,
|
|
|
+ "planId": req.PlanId,
|
|
|
+ "dayTo": req.DayTo,
|
|
|
+ "period": req.Period,
|
|
|
+ "countPeriod": req.CountPeriod,
|
|
|
+ "name": req.Name,
|
|
|
+ "isFree": req.IsFree,
|
|
|
+ "periodDayTo": req.PeriodDayTo,
|
|
|
}
|
|
|
apiUrl := s.Url + "UserPlanService/buyUserPlan"
|
|
|
- resBody, err := s.SendData(ctx, formData, apiUrl)
|
|
|
+ resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
|
|
|
if err != nil {
|
|
|
return 0, err
|
|
|
}
|
|
@@ -184,7 +245,7 @@ func (s *cdnService) BindPlan(ctx context.Context, req v1.Plan) (int64, error) {
|
|
|
}
|
|
|
var res v1.GeneralResponse[DataStr]
|
|
|
if err := json.Unmarshal(resBody, &res); err != nil {
|
|
|
- return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
|
|
|
+ return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
|
|
|
}
|
|
|
if res.Code != 200 {
|
|
|
return 0, fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Message)
|
|
@@ -198,17 +259,17 @@ func (s *cdnService) BindPlan(ctx context.Context, req v1.Plan) (int64, error) {
|
|
|
//续费套餐
|
|
|
func (s *cdnService) RenewPlan(ctx context.Context, req v1.RenewalPlan) error {
|
|
|
formData := map[string]interface{}{
|
|
|
- "userPlanId": req.UserPlanId,
|
|
|
- "dayTo": req.DayTo,
|
|
|
- "period": req.Period,
|
|
|
- "countPeriod": req.CountPeriod,
|
|
|
- "isFree": req.IsFree,
|
|
|
- "periodDayTo": req.PeriodDayTo,
|
|
|
+ "userPlanId": req.UserPlanId,
|
|
|
+ "dayTo": req.DayTo,
|
|
|
+ "period": req.Period,
|
|
|
+ "countPeriod": req.CountPeriod,
|
|
|
+ "isFree": req.IsFree,
|
|
|
+ "periodDayTo": req.PeriodDayTo,
|
|
|
}
|
|
|
apiUrl := s.Url + "UserPlanService/renewUserPlan"
|
|
|
- resBody, err := s.SendData(ctx, formData, apiUrl)
|
|
|
+ resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
|
|
|
if err != nil {
|
|
|
- return err
|
|
|
+ return err
|
|
|
}
|
|
|
var res v1.GeneralResponse[any]
|
|
|
if err := json.Unmarshal(resBody, &res); err != nil {
|
|
@@ -220,28 +281,27 @@ func (s *cdnService) RenewPlan(ctx context.Context, req v1.RenewalPlan) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-
|
|
|
//创建网站
|
|
|
func (s *cdnService) CreateWebsite(ctx context.Context, req v1.Website) (int64, error) {
|
|
|
formData := map[string]interface{}{
|
|
|
- "userId": req.UserId,
|
|
|
- "type": req.Type,
|
|
|
- "name": req.Name,
|
|
|
- "description": req.Description,
|
|
|
- "serverNamesJSON": req.ServerNamesJSON,
|
|
|
- "httpJSON": req.HttpJSON,
|
|
|
- "httpsJSON": req.HttpsJSON,
|
|
|
- "tcpJSON": req.TcpJSON,
|
|
|
- "tlsJSON": req.TlsJSON,
|
|
|
- "udpJSON": req.UdpJSON,
|
|
|
- "webId": req.WebId,
|
|
|
- "reverseProxyJSON": req.ReverseProxyJSON,
|
|
|
- "serverGroupIds": req.ServerGroupIds,
|
|
|
- "userPlanId": req.UserPlanId,
|
|
|
- "nodeClusterId": req.NodeClusterId,
|
|
|
+ "userId": req.UserId,
|
|
|
+ "type": req.Type,
|
|
|
+ "name": req.Name,
|
|
|
+ "description": req.Description,
|
|
|
+ "serverNamesJSON": req.ServerNamesJSON,
|
|
|
+ "httpJSON": req.HttpJSON,
|
|
|
+ "httpsJSON": req.HttpsJSON,
|
|
|
+ "tcpJSON": req.TcpJSON,
|
|
|
+ "tlsJSON": req.TlsJSON,
|
|
|
+ "udpJSON": req.UdpJSON,
|
|
|
+ "webId": req.WebId,
|
|
|
+ "reverseProxyJSON": req.ReverseProxyJSON,
|
|
|
+ "serverGroupIds": req.ServerGroupIds,
|
|
|
+ "userPlanId": req.UserPlanId,
|
|
|
+ "nodeClusterId": req.NodeClusterId,
|
|
|
}
|
|
|
apiUrl := s.Url + "ServerService/createServer"
|
|
|
- resBody, err := s.SendData(ctx, formData, apiUrl)
|
|
|
+ resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
|
|
|
if err != nil {
|
|
|
return 0, err
|
|
|
}
|
|
@@ -250,7 +310,7 @@ func (s *cdnService) CreateWebsite(ctx context.Context, req v1.Website) (int64,
|
|
|
}
|
|
|
var res v1.GeneralResponse[DataStr]
|
|
|
if err := json.Unmarshal(resBody, &res); err != nil {
|
|
|
- return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
|
|
|
+ return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
|
|
|
}
|
|
|
if res.Code != 200 {
|
|
|
return 0, fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Message)
|
|
@@ -261,7 +321,7 @@ func (s *cdnService) CreateWebsite(ctx context.Context, req v1.Website) (int64,
|
|
|
return res.Data.WebsiteId, nil
|
|
|
}
|
|
|
|
|
|
-func (s *cdnService) EditProtocol(ctx context.Context, req v1.ProxyJson,action string) error {
|
|
|
+func (s *cdnService) EditProtocol(ctx context.Context, req v1.ProxyJson, action string) error {
|
|
|
formData := map[string]interface{}{
|
|
|
"serverId": req.ServerId,
|
|
|
}
|
|
@@ -285,7 +345,7 @@ func (s *cdnService) EditProtocol(ctx context.Context, req v1.ProxyJson,action s
|
|
|
default:
|
|
|
return fmt.Errorf("不支持的协议类型")
|
|
|
}
|
|
|
- resBody, err := s.SendData(ctx, formData, apiUrl)
|
|
|
+ resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
@@ -299,23 +359,23 @@ func (s *cdnService) EditProtocol(ctx context.Context, req v1.ProxyJson,action s
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (s *cdnService) CreateOrigin(ctx context.Context, req v1.Origin) (int64, error) {
|
|
|
+func (s *cdnService) CreateOrigin(ctx context.Context, req v1.Origin) (int64, error) {
|
|
|
formData := map[string]interface{}{
|
|
|
- "name": req.Name,
|
|
|
- "addr": req.Addr,
|
|
|
- "ossJSON": req.OssJSON,
|
|
|
- "description": req.Description,
|
|
|
- "weight": req.Weight,
|
|
|
- "isOn": req.IsOn,
|
|
|
- "domains": req.Domains,
|
|
|
- "certRefJSON": req.CertRefJSON,
|
|
|
- "host": req.Host,
|
|
|
- "followPort": req.FollowPort,
|
|
|
- "http2Enabled": req.Http2Enabled,
|
|
|
+ "name": req.Name,
|
|
|
+ "addr": req.Addr,
|
|
|
+ "ossJSON": req.OssJSON,
|
|
|
+ "description": req.Description,
|
|
|
+ "weight": req.Weight,
|
|
|
+ "isOn": req.IsOn,
|
|
|
+ "domains": req.Domains,
|
|
|
+ "certRefJSON": req.CertRefJSON,
|
|
|
+ "host": req.Host,
|
|
|
+ "followPort": req.FollowPort,
|
|
|
+ "http2Enabled": req.Http2Enabled,
|
|
|
"tlsSecurityVerifyMode": req.TlsSecurityVerifyMode,
|
|
|
}
|
|
|
apiUrl := s.Url + "OriginService/createOrigin"
|
|
|
- resBody, err := s.SendData(ctx, formData, apiUrl)
|
|
|
+ resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
|
|
|
if err != nil {
|
|
|
return 0, err
|
|
|
}
|
|
@@ -324,7 +384,7 @@ func (s *cdnService) CreateOrigin(ctx context.Context, req v1.Origin) (int64, er
|
|
|
}
|
|
|
var res v1.GeneralResponse[DataStr]
|
|
|
if err := json.Unmarshal(resBody, &res); err != nil {
|
|
|
- return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
|
|
|
+ return 0, fmt.Errorf("反序列化响应 JSON 失败 (内容: %s): %w", string(resBody), err)
|
|
|
}
|
|
|
if res.Code != 200 {
|
|
|
return 0, fmt.Errorf("API 错误: code %d, msg '%s'", res.Code, res.Message)
|
|
@@ -337,22 +397,22 @@ func (s *cdnService) CreateOrigin(ctx context.Context, req v1.Origin) (int64, er
|
|
|
|
|
|
func (s *cdnService) EditOrigin(ctx context.Context, req v1.Origin) error {
|
|
|
formData := map[string]interface{}{
|
|
|
- "originId": req.OriginId,
|
|
|
- "name": req.Name,
|
|
|
- "addr": req.Addr,
|
|
|
- "ossJSON": req.OssJSON,
|
|
|
- "description": req.Description,
|
|
|
- "weight": req.Weight,
|
|
|
- "isOn": req.IsOn,
|
|
|
- "domains": req.Domains,
|
|
|
- "certRefJSON": req.CertRefJSON,
|
|
|
- "host": req.Host,
|
|
|
- "followPort": req.FollowPort,
|
|
|
- "http2Enabled": req.Http2Enabled,
|
|
|
+ "originId": req.OriginId,
|
|
|
+ "name": req.Name,
|
|
|
+ "addr": req.Addr,
|
|
|
+ "ossJSON": req.OssJSON,
|
|
|
+ "description": req.Description,
|
|
|
+ "weight": req.Weight,
|
|
|
+ "isOn": req.IsOn,
|
|
|
+ "domains": req.Domains,
|
|
|
+ "certRefJSON": req.CertRefJSON,
|
|
|
+ "host": req.Host,
|
|
|
+ "followPort": req.FollowPort,
|
|
|
+ "http2Enabled": req.Http2Enabled,
|
|
|
"tlsSecurityVerifyMode": req.TlsSecurityVerifyMode,
|
|
|
}
|
|
|
apiUrl := s.Url + "OriginService/updateOrigin"
|
|
|
- resBody, err := s.SendData(ctx, formData, apiUrl)
|
|
|
+ resBody, err := s.sendDataWithTokenRetry(ctx, formData, apiUrl) // 使用封装后的方法
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|