MingTone滴小站

MingTone滴小站



记录一下api前后端的代码

MingTone · 2025-03-09 · 61浏览 · Api相关,网站



架构设计

CDN ➡ 源 Nginx ➡ 后端 GO

后端代码(Go 实现)

package main

import (
    "fmt"
    "math/rand"
    "os"
    "path/filepath"
    "regexp"
    "runtime"
    "sync"
    "time"

    "github.com/valyala/fasthttp"
)

// 图片存储路径
const (
    largeDir = "/data/furryapi/large"
    boxDir   = "/data/furryapi/box"
)

// 计数存储文件
const counterFile = "counter.json"

var (
    largeImages []string
    boxImages   []string
    cacheMutex  sync.RWMutex
    rng         *rand.Rand
    once        sync.Once
    apiCalls    int
    counterLock sync.Mutex
)

// 初始化随机数
func initRNG() {
    once.Do(func() {
        rng = rand.New(rand.NewSource(time.Now().UnixNano()))
    })
}

// 读取目录中的图片
func loadImages(dir string) ([]string, error) {
    files, err := os.ReadDir(dir)
    if err != nil {
        return nil, err
    }

    var images []string
    for _, file := range files {
        if !file.IsDir() && filepath.Ext(file.Name()) == ".webp" {
            images = append(images, filepath.Join(dir, file.Name()))
        }
    }
    return images, nil
}

// 定期刷新图片缓存
func refreshCache() {
    for {
        large, _ := loadImages(largeDir)
        box, _ := loadImages(boxDir)

        cacheMutex.Lock()
        largeImages, boxImages = large, box
        cacheMutex.Unlock()

        time.Sleep(300 * time.Second)
    }
}

// 获取随机图片
func getRandomImage(images []string) (string, error) {
    cacheMutex.RLock()
    defer cacheMutex.RUnlock()

    if len(images) == 0 {
        return "", fmt.Errorf("no images found")
    }
    return images[rng.Intn(len(images))], nil
}

// 判断是否是移动设备
func isMobileUser(userAgent []byte) bool {
    mobileRegex := regexp.MustCompile(`Mobile|Android|BlackBerry|iPhone|Windows Phone`)
    return mobileRegex.Match(userAgent)
}

// 加载 API 访问计数
func loadCounter() {
    counterLock.Lock()
    defer counterLock.Unlock()

    if _, err := os.Stat(counterFile); os.IsNotExist(err) {
        fmt.Println("No existing counter file found. Starting fresh.")
        return
    }

    data, err := os.ReadFile(counterFile)
    if err != nil {
        fmt.Println("Failed to read counter file:", err)
        return
    }

    _, err = fmt.Sscanf(string(data), "%d", &apiCalls)
    if err != nil {
        fmt.Println("Failed to parse counter file:", err)
    }
}

// 保存 API 访问计数
func saveCounter() {
    counterLock.Lock()
    defer counterLock.Unlock()

    data := fmt.Sprintf("%d", apiCalls)
    err := os.WriteFile(counterFile, []byte(data), 0644)
    if err != nil {
        fmt.Println("Failed to save counter file:", err)
    }
}

// 定期保存访问计数
func autoSaveCounter() {
    for {
        time.Sleep(10 * time.Second)
        saveCounter()
    }
}

// 增加 API 访问计数
func incrementCounter() {
    counterLock.Lock()
    apiCalls++
    fmt.Println("API 调用次数:", apiCalls)
    counterLock.Unlock()
}

// API 处理函数
func handler(ctx *fasthttp.RequestCtx) {
    incrementCounter() // 统计 API 调用次数

    isMobile := isMobileUser(ctx.UserAgent())

    var selectedImages []string
    if isMobile {
        selectedImages = boxImages
    } else {
        selectedImages = largeImages
    }

    imagePath, err := getRandomImage(selectedImages)
    if err != nil {
        ctx.SetStatusCode(fasthttp.StatusNotFound)
        ctx.SetBodyString("No images found.")
        return
    }

    ctx.SendFile(imagePath)
}

// 同步访问次数
func notifyCounterService() {
    req := fasthttp.AcquireRequest()
    defer fasthttp.ReleaseRequest(req)
    res := fasthttp.AcquireResponse()
    defer fasthttp.ReleaseResponse(res)

    req.SetRequestURI("http://127.0.0.1:2474/counter?host=furryapi")
    req.Header.SetMethod("GET")

    client := &fasthttp.Client{}
    err := client.Do(req, res)
    if err != nil {
        fmt.Println("Failed to update counter service:", err)
    }
}

// 定期同步访问次数
func autoSyncToCounterService() {
    for {
        time.Sleep(10 * time.Second)
        notifyCounterService()
    }
}

func main() {
    initRNG()
    loadCounter()
    go refreshCache()
    go autoSaveCounter()
    go autoSyncToCounterService()

    runtime.GOMAXPROCS(runtime.NumCPU())

    server := fasthttp.Server{
        Handler:      handler,
        ReadTimeout:  3 * time.Second,
        WriteTimeout: 3 * time.Second,
        IdleTimeout:  10 * time.Second,
    }

    fmt.Println("Server running on port 889...")
    server.ListenAndServe(":889")
}

计数功能(API 访问 & 网页访问)

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "sync"
    "time"
)

// 计数存储文件
const counterFile = "counter.json"

// 访问计数器(修正 copylock 问题)
type Counter struct {
    mu        *sync.Mutex `json:"-"` // 变为指针,防止 `json.Marshal` 复制 lock
    APICalls  int         `json:"api_calls"`
    WebVisits int         `json:"web_visits"`
}

// 计数器实例
var counter = Counter{
    mu:        new(sync.Mutex), // 初始化 `sync.Mutex` 指针
    APICalls:  0,
    WebVisits: 0,
}

// 加载访问计数数据
func loadCounter() {
    counter.mu.Lock()
    defer counter.mu.Unlock()

    if _, err := os.Stat(counterFile); os.IsNotExist(err) {
        fmt.Println("No existing counter file found. Starting fresh.")
        return
    }

    data, err := os.ReadFile(counterFile)
    if err != nil {
        fmt.Println("Failed to read counter file:", err)
        return
    }

    err = json.Unmarshal(data, &counter)
    if err != nil {
        fmt.Println("Failed to parse counter file:", err)
    }
}

// 保存访问计数数据
func saveCounter() {
    counter.mu.Lock()
    defer counter.mu.Unlock()

    // 复制一个新结构体(不包含 `sync.Mutex`)
    safeCounter := Counter{
        APICalls:  counter.APICalls,
        WebVisits: counter.WebVisits,
    }

    data, err := json.MarshalIndent(safeCounter, "", "  ")
    if err != nil {
        fmt.Println("Failed to serialize counter:", err)
        return
    }

    err = os.WriteFile(counterFile, data, 0644)
    if err != nil {
        fmt.Println("Failed to save counter file:", err)
    }
}

// 定期保存访问数据
func autoSaveCounter() {
    for {
        time.Sleep(10 * time.Second)
        saveCounter()
    }
}

// 处理计数请求(网页访问 + API 访问)
func counterHandler(w http.ResponseWriter, r *http.Request) {
    host := r.URL.Query().Get("host")

    counter.mu.Lock()
    if host == "api.furry.ist" {
        counter.WebVisits++ // 统计网页访问
    } else if host == "furryapi" {
        counter.APICalls++ // 统计 API 访问
    }
    fmt.Printf("网页访问量: %d, API 访问量: %d\n", counter.WebVisits, counter.APICalls)
    counter.mu.Unlock()

    // 允许 CORS 访问
    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Content-Type", "application/json")

    // 返回统计信息
    response := fmt.Sprintf(`{"total_views": %d, "web_views": %d, "api_views": %d}`,
        counter.WebVisits+counter.APICalls, counter.WebVisits, counter.APICalls)
    w.Write([]byte(response))
}

func main() {
    loadCounter()
    go autoSaveCounter()

    http.HandleFunc("/counter", counterHandler)

    fmt.Println("Counter API running on http://0.0.0.0:2474")
    http.ListenAndServe(":2474", nil)
}




comment 评论区

添加新评论





  • ©2025 bilibili.com

textsms
内容不能为空
account_circle
昵称不能为空
email
邮件地址格式错误
web
beach_access
验证码不能为空
keyboard发表评论


star_outline 咱快来抢个沙发吧!




©2025 MingTone滴小站

Theme Romanticism2.1 by Akashi
Powered by Typecho