更新环境配置,优化数据库和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:
Vendored
+8
@@ -23,6 +23,14 @@ func NewBrandCache(rdb *redis.Client) *BrandCache {
|
||||
return &BrandCache{rdb: rdb}
|
||||
}
|
||||
|
||||
func (c *BrandCache) Exists(ctx context.Context) (bool, error) {
|
||||
n, err := c.rdb.Exists(ctx, brandAllKey).Result()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
func (c *BrandCache) GetAll(ctx context.Context) ([]model.Brand, error) {
|
||||
data, err := c.rdb.Get(ctx, brandAllKey).Bytes()
|
||||
if err != nil {
|
||||
|
||||
Vendored
+8
@@ -24,6 +24,14 @@ func NewModelCache(rdb *redis.Client) *ModelCache {
|
||||
return &ModelCache{rdb: rdb}
|
||||
}
|
||||
|
||||
func (c *ModelCache) Exists(ctx context.Context) (bool, error) {
|
||||
n, err := c.rdb.Exists(ctx, modelAllKey).Result()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
func modelBrandKey(brandName string) string {
|
||||
return modelBrandPrefix + brandName
|
||||
}
|
||||
|
||||
@@ -15,12 +15,11 @@ type DatabaseConfig struct {
|
||||
}
|
||||
|
||||
func loadDatabase(env string) (DatabaseConfig, error) {
|
||||
if os.Getenv("DATABASE_HOST") != "" {
|
||||
return databaseFromEnv()
|
||||
}
|
||||
|
||||
switch env {
|
||||
case "production":
|
||||
if os.Getenv("DATABASE_HOST") != "" {
|
||||
return databaseFromEnv()
|
||||
}
|
||||
return DatabaseConfig{
|
||||
Host: "database-1.chmuueamo72p.eu-central-1.rds.amazonaws.com",
|
||||
Port: 3306,
|
||||
@@ -29,12 +28,16 @@ func loadDatabase(env string) (DatabaseConfig, error) {
|
||||
Password: os.Getenv("DATABASE_PASSWORD"),
|
||||
}, nil
|
||||
default:
|
||||
port, err := strconv.Atoi(getEnv("DATABASE_PORT", "3306"))
|
||||
if err != nil {
|
||||
return DatabaseConfig{}, fmt.Errorf("invalid DATABASE_PORT: %w", err)
|
||||
}
|
||||
return DatabaseConfig{
|
||||
Host: "192.168.9.137",
|
||||
Port: 3306,
|
||||
Name: "audio",
|
||||
User: "root",
|
||||
Password: "root123",
|
||||
Host: getEnv("DATABASE_HOST", "192.168.9.127"),
|
||||
Port: port,
|
||||
Name: getEnv("DATABASE_NAME", "audio"),
|
||||
User: getEnv("DATABASE_USER", "root"),
|
||||
Password: getEnv("DATABASE_PASSWORD", "root123"),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,32 +3,18 @@ package config
|
||||
import "os"
|
||||
|
||||
type EqualizeConfig struct {
|
||||
APIURL string
|
||||
MeasurementBasePath string
|
||||
TargetBasePath string
|
||||
APIURL string
|
||||
}
|
||||
|
||||
func loadEqualize(env string) EqualizeConfig {
|
||||
if os.Getenv("EQ_API_URL") != "" {
|
||||
return EqualizeConfig{
|
||||
APIURL: os.Getenv("EQ_API_URL"),
|
||||
MeasurementBasePath: getEnv("MEASUREMENT_BASE_PATH", "/app/measurements"),
|
||||
TargetBasePath: getEnv("TARGET_BASE_PATH", "/app/targets"),
|
||||
}
|
||||
return EqualizeConfig{APIURL: os.Getenv("EQ_API_URL")}
|
||||
}
|
||||
|
||||
switch env {
|
||||
case "production":
|
||||
return EqualizeConfig{
|
||||
APIURL: "http://172.31.18.70:8000/equalize",
|
||||
MeasurementBasePath: getEnv("MEASUREMENT_BASE_PATH", "/app/measurements"),
|
||||
TargetBasePath: getEnv("TARGET_BASE_PATH", "/app/targets"),
|
||||
}
|
||||
return EqualizeConfig{APIURL: "http://172.31.18.70:8000/equalize"}
|
||||
default:
|
||||
return EqualizeConfig{
|
||||
APIURL: "https://autoeq.app/equalize",
|
||||
MeasurementBasePath: getEnv("MEASUREMENT_BASE_PATH", "/app/measurements"),
|
||||
TargetBasePath: getEnv("TARGET_BASE_PATH", "/app/targets"),
|
||||
}
|
||||
return EqualizeConfig{APIURL: "https://autoeq.app/equalize"}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-10
@@ -14,12 +14,11 @@ type RedisConfig struct {
|
||||
}
|
||||
|
||||
func loadRedis(env string) RedisConfig {
|
||||
if os.Getenv("REDIS_HOST") != "" {
|
||||
return redisFromEnv()
|
||||
}
|
||||
|
||||
switch env {
|
||||
case "production":
|
||||
if os.Getenv("REDIS_HOST") != "" {
|
||||
return redisFromEnv("16279")
|
||||
}
|
||||
return RedisConfig{
|
||||
Host: "172.31.38.162",
|
||||
Port: 16279,
|
||||
@@ -27,17 +26,19 @@ func loadRedis(env string) RedisConfig {
|
||||
Database: 1,
|
||||
}
|
||||
default:
|
||||
port, _ := strconv.Atoi(getEnv("REDIS_PORT", "6379"))
|
||||
db, _ := strconv.Atoi(getEnv("REDIS_DATABASE", "1"))
|
||||
return RedisConfig{
|
||||
Host: "192.168.9.137",
|
||||
Port: 6379,
|
||||
Password: "",
|
||||
Database: 1,
|
||||
Host: getEnv("REDIS_HOST", "192.168.9.127"),
|
||||
Port: port,
|
||||
Password: getEnv("REDIS_PASSWORD", ""),
|
||||
Database: db,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func redisFromEnv() RedisConfig {
|
||||
port, _ := strconv.Atoi(getEnv("REDIS_PORT", "16279"))
|
||||
func redisFromEnv(defaultPort string) RedisConfig {
|
||||
port, _ := strconv.Atoi(getEnv("REDIS_PORT", defaultPort))
|
||||
db, _ := strconv.Atoi(getEnv("REDIS_DATABASE", "1"))
|
||||
|
||||
return RedisConfig{
|
||||
|
||||
+25
-51
@@ -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
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -40,7 +40,7 @@ func New(log *zap.Logger, db *sql.DB, searchClient *search.Client, rdb *redis.Cl
|
||||
modelList := handler.NewModelListHandler(searchClient, log)
|
||||
device := handler.NewDeviceHandler(rdb, log)
|
||||
ota := handler.NewOTAHandler(otaRepo, log)
|
||||
curve := handler.NewCurveHandler(curveRepo, curveCache, eqCfg, log)
|
||||
curve := handler.NewCurveHandler(curveRepo, curveCache, eqCfg, s3, log)
|
||||
modelCSV := handler.NewModelCSVHandler(s3, log)
|
||||
|
||||
v1 := r.Group("/api/v1")
|
||||
|
||||
Reference in New Issue
Block a user