架构设计
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 评论区
star_outline 咱快来抢个沙发吧!