Skip to content

Go 服务端示例 ​

本示例展示如何在 Go 服务端实现定时拉取配置、缓存密文、并向客户端提供接口。

示例使用标准库 + go-redis,可根据实际技术栈调整。

接口域名见服务端接入说明 · 环境说明。


依赖 ​

bash
go get github.com/redis/go-redis/v9

完整示例 ​

go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"time"

	"github.com/redis/go-redis/v9"
)

const (
	configAPI    = "https://api.dc-server.cc/sdk/getAvailableDomainList"
	cacheKey     = "sdk:encrypted_config"
	cacheTTL     = 30 * time.Minute // Redis TTL:30 分钟(拉取间隔的 3 倍)
	fetchInterval = 10 * time.Minute
)

var rdb *redis.Client

func init() {
	rdb = redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})
}

// fetchAndCache 拉取配置并缓存
func fetchAndCache(ctx context.Context) {
	client := &http.Client{Timeout: 5 * time.Second}

	resp, err := client.Get(configAPI)
	if err != nil {
		log.Printf("[SdkConfig] 拉取异常,沿用上次缓存: %v", err)
		return
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)

	var result struct {
		Code int    `json:"code"`
		Msg  string `json:"msg"`
		Data string `json:"data"`
	}
	if err := json.Unmarshal(body, &result); err != nil {
		log.Printf("[SdkConfig] 响应解析失败,沿用上次缓存: %v", err)
		return
	}

	if result.Code == 0 && result.Data != "" {
		// 成功:更新缓存,重置 TTL
		if err := rdb.Set(ctx, cacheKey, result.Data, cacheTTL).Err(); err != nil {
			log.Printf("[SdkConfig] Redis 写入失败: %v", err)
			return
		}
		log.Println("[SdkConfig] 拉取成功,已更新缓存")
	} else {
		// 失败:保留上次缓存,不清空
		log.Printf("[SdkConfig] 拉取失败,code=%d,沿用上次缓存", result.Code)
	}
}

// startScheduler 启动定时任务
func startScheduler(ctx context.Context) {
	// 启动时立即拉取一次
	fetchAndCache(ctx)

	ticker := time.NewTicker(fetchInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			fetchAndCache(ctx)
		case <-ctx.Done():
			return
		}
	}
}

// getSdkConfigHandler 向客户端提供密文
func getSdkConfigHandler(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	w.Header().Set("Content-Type", "application/json")

	encryptedData, err := rdb.Get(ctx, cacheKey).Result()
	if err == redis.Nil || encryptedData == "" {
		// 无缓存:返回错误状态
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, `{"code":-1,"msg":"config not available"}`)
		return
	}
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(w, `{"code":-1,"msg":"internal error"}`)
		return
	}

	// 密文原样返回,不解密
	resp := map[string]interface{}{
		"code": 0,
		"msg":  "success",
		"data": encryptedData,
	}
	json.NewEncoder(w).Encode(resp)
}

func main() {
	ctx := context.Background()

	// 启动定时拉取任务(后台 goroutine)
	go startScheduler(ctx)

	// 注册 HTTP 路由
	http.HandleFunc("/your-api/sdk-config", getSdkConfigHandler)

	log.Println("服务启动,监听 :8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

关键说明 ​

  • 不解密 data:服务端只存储密文字符串,不尝试解密
  • 失败兜底:拉取失败(网络异常、非 0 code)时不清空 Redis 缓存,沿用上次密文
  • TTL 兜底:Redis TTL 设为 30 分钟,防止极端情况下缓存意外过期
  • 启动时立即拉取:startScheduler 启动后先执行一次拉取,不等第一个 tick
  • 5 秒超时:HTTP 客户端设置超时,避免阻塞

相关文档 ​