更新环境配置,优化数据库和Redis连接设置

- 修改`.env.example`文件,更新数据库和Redis的默认主机地址为`192.168.9.127`
- 在`docker-compose.yml`中移除不必要的卷挂载配置
- 在`go.mod`中添加`github.com/joho/godotenv`依赖以支持环境变量加载
- 更新`README.md`以反映新的数据库和Redis连接信息
- 在`main.go`中加载环境变量以支持动态配置
- 新增缓存存在性检查功能,避免重复预热缓存
- 优化CSV读取逻辑,支持从S3获取数据
This commit is contained in:
eafonyang
2026-06-08 14:52:23 +08:00
parent 99785672a5
commit 87bdad34fc
15 changed files with 136 additions and 110 deletions
+25 -51
View File
@@ -3,22 +3,23 @@ package handler
import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/gin-gonic/gin"
"github.com/luxsin/app-api/internal/cache"
"github.com/luxsin/app-api/internal/config"
"github.com/luxsin/app-api/internal/repository"
"github.com/luxsin/app-api/internal/response"
"github.com/luxsin/app-api/internal/storage"
"github.com/luxsin/app-api/pkg/encode"
"go.uber.org/zap"
)
@@ -27,13 +28,14 @@ type CurveHandler struct {
repo *repository.CurveRepository
cache *cache.CurveCache
cfg config.EqualizeConfig
s3 *storage.S3Storage
log *zap.Logger
}
const defaultModelCurveTarget = "Harman over-ear 2018"
func NewCurveHandler(repo *repository.CurveRepository, curveCache *cache.CurveCache, cfg config.EqualizeConfig, log *zap.Logger) *CurveHandler {
return &CurveHandler{repo: repo, cache: curveCache, cfg: cfg, log: log}
func NewCurveHandler(repo *repository.CurveRepository, curveCache *cache.CurveCache, cfg config.EqualizeConfig, s3 *storage.S3Storage, log *zap.Logger) *CurveHandler {
return &CurveHandler{repo: repo, cache: curveCache, cfg: cfg, s3: s3, log: log}
}
// ModelCurve 获取机型默认曲线(固定 target: Harman over-ear 2018
@@ -82,7 +84,8 @@ func (h *CurveHandler) ModelCurve(c *gin.Context) {
func (h *CurveHandler) GetCurve(c *gin.Context) {
brand := strings.TrimSpace(queryParam(c, "brand"))
name := strings.TrimSpace(queryParam(c, "name"))
target := strings.TrimSpace(queryParam(c, "target"))
// target 使用标准 query 解码:+ 表示空格(如 Harman+over-ear+2018 → Harman over-ear 2018
target := strings.TrimSpace(c.Query("target"))
base64Resp := encode.ParseBase64Param(c)
if brand == "" || name == "" || target == "" {
@@ -215,10 +218,11 @@ func (h *CurveHandler) getCurvePointFromPEQ(ctx context.Context, brand, name, ta
headPhone = name
}
// 获取 measurement 数据(仅 Eafonyoung 源需要读 CSV
// 获取 measurement 数据(仅 Eafonyoung 源需要从 S3 读 CSV
var measurement map[string]any
if m.Source != nil && *m.Source == "Eafonyoung" {
measurement, err = h.readCSV(h.cfg.MeasurementBasePath + "/Eafonyoung/data/" + deref(m.Form) + "/" + headPhone + ".csv")
key := modelCSVKey(brand, name, deref(m.Form))
measurement, err = h.readCSVFromS3(ctx, key)
if err != nil || measurement == nil {
return "", nil
}
@@ -229,7 +233,8 @@ func (h *CurveHandler) getCurvePointFromPEQ(ctx context.Context, brand, name, ta
var targetRaw map[string]any
if bool(t.ReadCSV) {
targetRaw, err = h.readCSV(h.cfg.TargetBasePath + "/" + deref(t.File))
key := targetCSVKey(deref(t.File))
targetRaw, err = h.readCSVFromS3(ctx, key)
if err != nil || targetRaw == nil {
return "", nil
}
@@ -369,55 +374,24 @@ func (h *CurveHandler) reqEqualize(headPhone string, measurement map[string]any,
return string(resultJSON), nil
}
// readCSV 读取 CSV 文件并返回 {frequency: [...], raw: [...]}
func (h *CurveHandler) readCSV(path string) (map[string]any, error) {
f, err := os.Open(path)
// readCSVFromS3 从 S3 读取 CSV 并返回 {frequency: [...], raw: [...]}
func (h *CurveHandler) readCSVFromS3(ctx context.Context, key string) (map[string]any, error) {
h.log.Info("reading csv from s3", zap.String("key", key))
data, err := h.s3.GetObject(ctx, key)
if err != nil {
if os.IsNotExist(err) {
h.log.Info("csv file not found", zap.String("path", path))
var noSuchKey *types.NoSuchKey
if errors.As(err, &noSuchKey) {
h.log.Info("csv not found in s3", zap.String("key", key))
return nil, nil
}
return nil, fmt.Errorf("open csv: %w", err)
}
defer f.Close()
reader := csv.NewReader(f)
// 跳过表头
if _, err := reader.Read(); err != nil {
return nil, fmt.Errorf("read csv header: %w", err)
return nil, fmt.Errorf("get csv from s3: %w", err)
}
frequency := make([]float64, 0)
raw := make([]float64, 0)
for {
record, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("read csv row: %w", err)
}
if len(record) < 2 {
continue
}
freq, err1 := strconv.ParseFloat(record[0], 64)
val, err2 := strconv.ParseFloat(record[1], 64)
if err1 != nil || err2 != nil {
continue
}
frequency = append(frequency, freq)
raw = append(raw, val)
parsed, err := parseCSVData(data)
if err != nil {
return nil, fmt.Errorf("parse csv: %w", err)
}
if len(frequency) == 0 {
return nil, nil
}
return map[string]any{
"frequency": frequency,
"raw": raw,
}, nil
return parsed, nil
}
// 辅助函数
+6
View File
@@ -46,6 +46,7 @@ func (h *ModelCSVHandler) GetModelCSV(c *gin.Context) {
key := modelCSVKey(brand, model, form)
ctx := c.Request.Context()
h.log.Info("reading csv from s3", zap.String("key", key))
data, err := h.s3.GetObject(ctx, key)
if err != nil {
var noSuchKey *types.NoSuchKey
@@ -86,6 +87,11 @@ func (h *ModelCSVHandler) GetModelCSV(c *gin.Context) {
})
}
func targetCSVKey(file string) string {
file = strings.TrimPrefix(strings.TrimSpace(file), "/")
return "autoeq/targets/" + file
}
func modelCSVKey(brand, model, form string) string {
filename := brand + " " + model + ".csv"
return fmt.Sprintf("autoeq/measurements/Eafonyoung/data/%s/%s/%s",