feat(equalize): 添加目标曲线获取及缓存功能

- 新增CurveCache实现Redis曲线数据缓存与分布式锁机制,避免重复请求
- 增加CurveHandler支持目标曲线接口,通过EQ API获取并缓存曲线数据
- Config新增EqualizeConfig配置,支持环境变量动态配置
- Repository新增CurveRepository,按brand和name查询模型与目标曲线数据
- 路由更新,集成/getCurve接口处理目标曲线请求
- OTA模型字段优化,bool类型改为BoolInt,实现数据库int与JSON bool映射
- 统一响应格式,调整response包中OK和Fail函数消息字段命名
- 修改服务端配置默认DB IP,支持Equalize参数传递到路由层
- 新增CSV解析实现,从文件读取测量与目标曲线数据
- 修改OTA处理逻辑,修复定向升级判断bug
- Java层新增EqualizeController和ModelService对应功能,保持客户端接口兼容与调用逻辑一致
This commit is contained in:
eafonyang
2026-06-01 19:08:43 +08:00
parent 665397ede7
commit c4cd8b6f5d
16 changed files with 1085 additions and 26 deletions
+88
View File
@@ -0,0 +1,88 @@
package cache
import (
"context"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
const (
curveLockTTL = 10 * time.Second
curveLockRetryDelay = 100 * time.Millisecond
curveLockMaxRetries = 50
)
// CurveCache 曲线数据缓存(Redis Hash
type CurveCache struct {
rdb *redis.Client
}
func NewCurveCache(rdb *redis.Client) *CurveCache {
return &CurveCache{rdb: rdb}
}
// Get 从 Redis Hash 中获取曲线缓存
func (c *CurveCache) Get(ctx context.Context, brand, name, target string) (string, error) {
key := brand + " " + name
val, err := c.rdb.HGet(ctx, key, target).Result()
if err == redis.Nil {
return "", nil
}
if err != nil {
return "", fmt.Errorf("hget curve cache: %w", err)
}
return val, nil
}
// Set 将曲线数据存入 Redis Hash
func (c *CurveCache) Set(ctx context.Context, brand, name, target, data string) error {
key := brand + " " + name
return c.rdb.HSet(ctx, key, target, data).Err()
}
// AcquireLock 获取分布式锁(SETNX),防止并发请求同一个曲线数据
func (c *CurveCache) AcquireLock(ctx context.Context, brand, name, target string) (bool, error) {
lockKey := brand + " " + name + ":" + target + ":lock"
return c.rdb.SetNX(ctx, lockKey, "locked", curveLockTTL).Result()
}
// ReleaseLock 释放分布式锁
func (c *CurveCache) ReleaseLock(ctx context.Context, brand, name, target string) error {
lockKey := brand + " " + name + ":" + target + ":lock"
return c.rdb.Del(ctx, lockKey).Err()
}
// GetWithLock 获取缓存数据,缓存不存在时尝试加锁后重新获取
// 返回值: (data, acquired, error)
// - data: 缓存数据(空字符串表示无数据)
// - acquired: 是否成功获取锁(缓存不存在时需要加锁)
func (c *CurveCache) GetWithLock(ctx context.Context, brand, name, target string) (data string, acquired bool, err error) {
// 先查缓存
data, err = c.Get(ctx, brand, name, target)
if err != nil {
return "", false, err
}
if data != "" {
return data, false, nil // 缓存命中
}
// 缓存不存在,尝试加锁
acquired, err = c.AcquireLock(ctx, brand, name, target)
if err != nil {
return "", false, fmt.Errorf("acquire curve lock: %w", err)
}
if acquired {
// 加锁成功,再次检查缓存(双重检查)
data, err = c.Get(ctx, brand, name, target)
if err != nil {
return "", true, err
}
return data, true, nil
}
// 未获取锁,等待后重试
return "", false, nil
}
+4
View File
@@ -13,6 +13,7 @@ type Config struct {
Database DatabaseConfig
Meilisearch MeilisearchConfig
Redis RedisConfig
Equalize EqualizeConfig
}
func Load() (*Config, error) {
@@ -41,6 +42,8 @@ func Load() (*Config, error) {
return nil, err
}
eq := loadEqualize(env)
return &Config{
Env: env,
Host: getEnv("APP_HOST", "0.0.0.0"),
@@ -48,6 +51,7 @@ func Load() (*Config, error) {
Database: db,
Meilisearch: ms,
Redis: rd,
Equalize: eq,
}, nil
}
+1 -1
View File
@@ -30,7 +30,7 @@ func loadDatabase(env string) (DatabaseConfig, error) {
}, nil
default:
return DatabaseConfig{
Host: "localhost",
Host: "192.168.9.137",
Port: 3306,
Name: "audio",
User: "root",
+34
View File
@@ -0,0 +1,34 @@
package config
import "os"
type EqualizeConfig struct {
APIURL string
MeasurementBasePath string
TargetBasePath 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", ""),
TargetBasePath: getEnv("TARGET_BASE_PATH", ""),
}
}
switch env {
case "production":
return EqualizeConfig{
APIURL: "https://autoeq.app/equalize",
MeasurementBasePath: getEnv("MEASUREMENT_BASE_PATH", ""),
TargetBasePath: getEnv("TARGET_BASE_PATH", ""),
}
default:
return EqualizeConfig{
APIURL: "https://autoeq.app/equalize",
MeasurementBasePath: getEnv("MEASUREMENT_BASE_PATH", ""),
TargetBasePath: getEnv("TARGET_BASE_PATH", ""),
}
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ func loadRedis(env string) RedisConfig {
}
default:
return RedisConfig{
Host: "localhost",
Host: "192.168.9.137",
Port: 6379,
Password: "",
Database: 1,
+392
View File
@@ -0,0 +1,392 @@
package handler
import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
"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"
"go.uber.org/zap"
)
type CurveHandler struct {
repo *repository.CurveRepository
cache *cache.CurveCache
cfg config.EqualizeConfig
log *zap.Logger
}
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}
}
// GetCurve 获取目标曲线
// GET /audio/getCurve?brand=xxx&name=xxx&target=xxx&includeRaw=false
func (h *CurveHandler) GetCurve(c *gin.Context) {
brand := strings.TrimSpace(c.Query("brand"))
name := strings.TrimSpace(c.Query("name"))
target := strings.TrimSpace(c.Query("target"))
includeRaw := c.Query("includeRaw") == "true" || c.Query("includeRaw") == "1"
if brand == "" || name == "" || target == "" {
response.BadRequest(c, "brand、name和target参数不能为空")
return
}
ctx := c.Request.Context()
result, err := h.getCurvePoint(ctx, brand, name, target)
if err != nil {
h.log.Error("get curve point failed", zap.Error(err))
response.InternalError(c, "获取曲线数据失败")
return
}
if result == "" {
response.Fail(c, http.StatusOK, 40004, "无曲线数据")
return
}
// 解析 JSON 结果
var resp map[string]any
if err := json.Unmarshal([]byte(result), &resp); err != nil {
response.InternalError(c, "解析曲线数据失败")
return
}
// 如果 includeRaw 为 false 且 code != 200,移除 fr 字段
if !includeRaw {
codeVal, _ := resp["code"]
if code, ok := codeVal.(float64); ok && code != 200 {
delete(resp, "fr")
}
}
response.OK(c, resp)
}
// getCurvePoint 获取曲线数据:先查缓存,缓存不存在则请求 EQ 接口并缓存结果
func (h *CurveHandler) getCurvePoint(ctx context.Context, brand, name, target string) (string, error) {
data, acquired, err := h.cache.GetWithLock(ctx, brand, name, target)
if err != nil {
return "", err
}
// 缓存命中
if data != "" && !acquired {
h.log.Info("curve cache hit", zap.String("brand", brand), zap.String("name", name), zap.String("target", target))
return data, nil
}
// 获取了锁,缓存仍然为空,需要请求 EQ 接口
if acquired {
result, eqErr := h.getCurvePointFromPEQ(ctx, brand, name, target)
if eqErr != nil {
// 释放锁
if lockErr := h.cache.ReleaseLock(ctx, brand, name, target); lockErr != nil {
h.log.Warn("release curve lock failed", zap.Error(lockErr))
}
return "", eqErr
}
// 释放锁
if lockErr := h.cache.ReleaseLock(ctx, brand, name, target); lockErr != nil {
h.log.Warn("release curve lock failed", zap.Error(lockErr))
}
if result != "" {
// 缓存结果
if cacheErr := h.cache.Set(ctx, brand, name, target, result); cacheErr != nil {
h.log.Warn("curve cache set failed", zap.Error(cacheErr))
}
return result, nil
}
return "", nil
}
// 未获取锁(其他请求正在处理),等待后重试
time.Sleep(100 * time.Millisecond)
return h.getCurvePoint(ctx, brand, name, target)
}
// getCurvePointFromPEQ 从 EQ 接口获取曲线数据
func (h *CurveHandler) getCurvePointFromPEQ(ctx context.Context, brand, name, targetName string) (string, error) {
m, err := h.repo.GetModelByBrandAndName(ctx, brand, name)
if err != nil {
return "", fmt.Errorf("query model: %w", err)
}
if m == nil {
return "", nil
}
t, err := h.repo.GetTargetByLabel(ctx, targetName)
if err != nil {
return "", fmt.Errorf("query target: %w", err)
}
if t == nil {
return "", nil
}
// 确定 headPhone 名称
headPhone := brand + " " + name
if m.EqKey != nil && *m.EqKey == "name" {
headPhone = name
}
// 获取 measurement 数据(仅 Eafonyoung 源需要读 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")
if err != nil || measurement == nil {
return "", nil
}
}
// 构建请求
var targetParam any
var targetRaw map[string]any
if bool(t.ReadCSV) {
targetRaw, err = h.readCSV(h.cfg.TargetBasePath + "/" + deref(t.File))
if err != nil || targetRaw == nil {
return "", nil
}
targetParam = targetRaw
} else {
targetParam = targetName
}
// 解析 bassBoost
var bassBoost map[string]any
if t.BassBoost != nil {
if err := json.Unmarshal([]byte(*t.BassBoost), &bassBoost); err != nil {
h.log.Warn("parse bassBoost failed", zap.Error(err))
bassBoost = map[string]any{"gain": 0, "fc": 100, "q": 0.7}
}
} else {
bassBoost = map[string]any{"gain": 0, "fc": 100, "q": 0.7}
}
resp, err := h.reqEqualize(headPhone, measurement, targetParam, bassBoost, deref(m.Source), deref(m.Rig))
if err != nil {
return "", err
}
return resp, nil
}
// reqEqualize 调用 autoeq API
func (h *CurveHandler) reqEqualize(headPhone string, measurement map[string]any, target any, bassBoost map[string]any, source, rig string) (string, error) {
gain := floatVal(bassBoost, "gain", 0)
fc := intVal(bassBoost, "fc", 100)
q := floatVal(bassBoost, "q", 0.7)
reqBody := map[string]any{
"target": target,
"sound_signature": nil,
"sound_signature_smoothing_window_size": 1,
"bass_boost_gain": gain,
"bass_boost_fc": fc,
"bass_boost_q": q,
"treble_boost_gain": 0,
"treble_boost_fc": 10000,
"treble_boost_q": 0.7,
"tilt": 0,
"fs": 48000,
"bit_depth": 16,
"phase": "minimum",
"f_res": 16,
"preamp": 0,
"max_gain": 12,
"max_slope": 18,
"window_size": 0.08,
"treble_window_size": 2,
"treble_f_lower": 6000,
"treble_f_upper": 8000,
"treble_gain_k": 1,
"graphic_eq": false,
"parametric_eq": true,
"fixed_band_eq": false,
"convolution_eq": false,
"source": source,
"rig": rig,
"parametric_eq_config": "MINIDSP_IL_DSP",
"response": map[string]any{
"fr_f_step": 1.02,
"base64fp16": false,
"fr_fields": []string{"raw"},
},
}
// measurement 或 headPhone 二选一
if measurement != nil {
reqBody["measurement"] = measurement
} else if headPhone != "" {
reqBody["name"] = headPhone
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return "", fmt.Errorf("marshal eq request: %w", err)
}
httpReq, err := http.NewRequestWithContext(context.Background(), http.MethodPost, h.cfg.APIURL, bytes.NewReader(jsonData))
if err != nil {
return "", fmt.Errorf("create eq request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second}
httpResp, err := client.Do(httpReq)
if err != nil {
return "", fmt.Errorf("call eq api: %w", err)
}
defer httpResp.Body.Close()
body, err := io.ReadAll(httpResp.Body)
if err != nil {
return "", fmt.Errorf("read eq response: %w", err)
}
if httpResp.StatusCode != http.StatusOK {
h.log.Warn("eq api returned non-200", zap.Int("status", httpResp.StatusCode), zap.String("body", string(body)))
return "", fmt.Errorf("eq api status: %d", httpResp.StatusCode)
}
var eqResp map[string]any
if err := json.Unmarshal(body, &eqResp); err != nil {
return "", fmt.Errorf("unmarshal eq response: %w", err)
}
parametricEq, _ := eqResp["parametric_eq"].(map[string]any)
if parametricEq == nil {
result := map[string]any{
"code": 200,
"msg": "req param error",
"param": reqBody,
}
resultJSON, _ := json.Marshal(result)
return string(resultJSON), nil
}
result := map[string]any{
"code": 200,
"msg": "ok",
"parametric_eq": eqResp["parametric_eq"],
"fr": eqResp["fr"],
}
resultJSON, _ := json.Marshal(result)
return string(resultJSON), nil
}
// readCSV 读取 CSV 文件并返回 {frequency: [...], raw: [...]}
func (h *CurveHandler) readCSV(path string) (map[string]any, error) {
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
h.log.Info("csv file not found", zap.String("path", path))
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)
}
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)
}
if len(frequency) == 0 {
return nil, nil
}
return map[string]any{
"frequency": frequency,
"raw": raw,
}, nil
}
// 辅助函数
func deref(s *string) string {
if s == nil {
return ""
}
return *s
}
func floatVal(m map[string]any, key string, defaultVal float64) float64 {
v, ok := m[key]
if !ok {
return defaultVal
}
switch n := v.(type) {
case float64:
return n
case int:
return float64(n)
case string:
f, err := strconv.ParseFloat(n, 64)
if err != nil {
return defaultVal
}
return f
default:
return defaultVal
}
}
func intVal(m map[string]any, key string, defaultVal int) int {
v, ok := m[key]
if !ok {
return defaultVal
}
switch n := v.(type) {
case float64:
return int(n)
case int:
return n
case string:
i, err := strconv.Atoi(n)
if err != nil {
return defaultVal
}
return i
default:
return defaultVal
}
}
+1 -1
View File
@@ -95,7 +95,7 @@ func (h *OTAHandler) GetOTA(c *gin.Context) {
}
// 3. 检查是否为定向升级
if ota.Target == 0 {
if !bool(ota.Target) {
// 非定向,直接返回
response.OK(c, ota)
return
+34 -10
View File
@@ -1,27 +1,51 @@
package model
import "time"
import (
"database/sql"
"time"
)
// BoolInt 数据库存储为 int(1/0)JSON 序列化为 bool(true/false)
type BoolInt bool
func (b *BoolInt) Scan(value interface{}) error {
if value == nil {
*b = false
return nil
}
switch v := value.(type) {
case int64:
*b = v == 1
case []byte:
*b = len(v) > 0 && v[0] == '1'
default:
*b = false
}
return nil
}
var _ sql.Scanner = (*BoolInt)(nil)
// OTA 固件升级记录
type OTA struct {
ID int `json:"id"`
ID int `json:"-"`
VerCode int `json:"verCode"`
VerName string `json:"verName"`
URL string `json:"url"`
MD5 string `json:"md5"`
Force int `json:"force"`
Force BoolInt `json:"force"`
Desc *string `json:"desc,omitempty"`
Model *string `json:"model,omitempty"`
HW int `json:"hw"`
Target int `json:"target"`
Beta int `json:"beta"`
PawVerCode int `json:"pawVerCode"`
PawVerName string `json:"pawVerName"`
PawURL string `json:"pawUrl"`
PawMD5 string `json:"pawMd5"`
Target BoolInt `json:"target"`
Beta BoolInt `json:"beta"`
PawVerCode int `json:"-"`
PawVerName string `json:"-"`
PawURL string `json:"-"`
PawMD5 string `json:"-"`
StartTime *time.Time `json:"startTime,omitempty"`
EndTime *time.Time `json:"endTime,omitempty"`
Status int `json:"status"`
Status int `json:"-"`
}
// BlackList OTA 黑名单
+13
View File
@@ -0,0 +1,13 @@
package model
import "time"
// Target 目标曲线
type Target struct {
ID int `json:"id"`
Label string `json:"label"`
ReadCSV BoolInt `json:"readCsv"`
File *string `json:"file,omitempty"`
BassBoost *string `json:"bassBoost,omitempty"`
CreateAt time.Time `json:"createAt"`
}
+65
View File
@@ -0,0 +1,65 @@
package repository
import (
"context"
"database/sql"
"fmt"
"github.com/luxsin/app-api/internal/model"
)
type CurveRepository struct {
db *sql.DB
}
func NewCurveRepository(db *sql.DB) *CurveRepository {
return &CurveRepository{db: db}
}
// GetModelByBrandAndName 按 brand_name + name 查询 Model(唯一索引)
func (r *CurveRepository) GetModelByBrandAndName(ctx context.Context, brandName, name string) (*model.Model, error) {
const query = `SELECT id, brand_name, name, form, rig, source, eq_key, create_at FROM model WHERE brand_name = ? AND name = ?`
var m model.Model
var form, rig, source, eqKey sql.NullString
err := r.db.QueryRowContext(ctx, query, brandName, name).Scan(
&m.ID, &m.BrandName, &m.Name, &form, &rig, &source, &eqKey, &m.CreateAt,
)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("query model: %w", err)
}
m.Form = nullStringPtr(form)
m.Rig = nullStringPtr(rig)
m.Source = nullStringPtr(source)
m.EqKey = nullStringPtr(eqKey)
return &m, nil
}
// GetTargetByLabel 按 label 查询 Target
func (r *CurveRepository) GetTargetByLabel(ctx context.Context, label string) (*model.Target, error) {
const query = `SELECT id, label, read_csv, file, bass_boost, create_at FROM target WHERE label = ?`
var t model.Target
var file, bassBoost sql.NullString
err := r.db.QueryRowContext(ctx, query, label).Scan(
&t.ID, &t.Label, &t.ReadCSV, &file, &bassBoost, &t.CreateAt,
)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("query target: %w", err)
}
t.File = nullStringPtr(file)
t.BassBoost = nullStringPtr(bassBoost)
return &t, nil
}
+3 -3
View File
@@ -18,7 +18,7 @@ func NewOTARepository(db *sql.DB) *OTARepository {
// GetLatestOTA 按 model+hw+beta+status=1 查询最新一条 OTA 记录(verCode 降序)
func (r *OTARepository) GetLatestOTA(ctx context.Context, modelVal string, hw, beta int) (*model.OTA, error) {
const query = `SELECT id, verCode, verName, url, md5, force, desc, model, hw, target, beta,
const query = `SELECT id, verCode, verName, url, md5, ` + "`force`" + `, ` + "`desc`" + `, model, hw, target, beta,
pawVerCode, pawVerName, pawUrl, pawMd5, startTime, endTime, status
FROM ota WHERE model = ? AND hw = ? AND beta = ? AND status = 1
ORDER BY verCode DESC LIMIT 1`
@@ -56,7 +56,7 @@ func (r *OTARepository) GetLatestOTA(ctx context.Context, modelVal string, hw, b
// GetLatestOTANotInBlackList 查询最新一条不在黑名单中的 OTA 记录
func (r *OTARepository) GetLatestOTANotInBlackList(ctx context.Context, modelVal string, hw, beta int, mac string) (*model.OTA, error) {
const query = `SELECT id, verCode, verName, url, md5, force, desc, model, hw, target, beta,
const query = `SELECT id, verCode, verName, url, md5, ` + "`force`" + `, ` + "`desc`" + `, model, hw, target, beta,
pawVerCode, pawVerName, pawUrl, pawMd5, startTime, endTime, status
FROM ota WHERE status = 1 AND model = ? AND hw = ? AND beta = ?
AND id NOT IN (SELECT ota_id FROM black_list WHERE mac = ?)
@@ -95,7 +95,7 @@ func (r *OTARepository) GetLatestOTANotInBlackList(ctx context.Context, modelVal
// GetLatestOTANotTarget 查询最新一条非定向的 OTA 记录
func (r *OTARepository) GetLatestOTANotTarget(ctx context.Context, modelVal string, hw, beta int) (*model.OTA, error) {
const query = `SELECT id, verCode, verName, url, md5, force, desc, model, hw, target, beta,
const query = `SELECT id, verCode, verName, url, md5, ` + "`force`" + `, ` + "`desc`" + `, model, hw, target, beta,
pawVerCode, pawVerName, pawUrl, pawMd5, startTime, endTime, status
FROM ota WHERE status = 1 AND model = ? AND hw = ? AND beta = ? AND target = 0
ORDER BY verCode DESC LIMIT 1`
+8 -8
View File
@@ -7,23 +7,23 @@ import (
)
type Body struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
Code int `json:"code"`
Msg string `json:"msg"`
Data any `json:"data,omitempty"`
}
func OK(c *gin.Context, data any) {
c.JSON(http.StatusOK, Body{
Code: 0,
Message: "ok",
Data: data,
Code: 200,
Msg: "操作成功",
Data: data,
})
}
func Fail(c *gin.Context, httpStatus int, code int, message string) {
c.JSON(httpStatus, Body{
Code: code,
Message: message,
Code: code,
Msg: message,
})
}
+6 -1
View File
@@ -5,6 +5,7 @@ import (
"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/handler"
"github.com/luxsin/app-api/internal/middleware"
"github.com/luxsin/app-api/internal/repository"
@@ -13,7 +14,7 @@ import (
"go.uber.org/zap"
)
func New(log *zap.Logger, db *sql.DB, searchClient *search.Client, rdb *redis.Client) *gin.Engine {
func New(log *zap.Logger, db *sql.DB, searchClient *search.Client, rdb *redis.Client, eqCfg config.EqualizeConfig) *gin.Engine {
r := gin.New()
r.Use(gin.Recovery())
r.Use(middleware.RequestID())
@@ -23,11 +24,13 @@ func New(log *zap.Logger, db *sql.DB, searchClient *search.Client, rdb *redis.Cl
// Cache
brandCache := cache.NewBrandCache(rdb)
modelCache := cache.NewModelCache(rdb)
curveCache := cache.NewCurveCache(rdb)
// Repository
brandRepo := repository.NewBrandRepository(db, brandCache)
modelRepo := repository.NewModelRepository(db, modelCache)
otaRepo := repository.NewOTARepository(db)
curveRepo := repository.NewCurveRepository(db)
// Handler
health := handler.NewHealthHandler()
@@ -36,6 +39,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)
v1 := r.Group("/api/v1")
{
@@ -49,6 +53,7 @@ func New(log *zap.Logger, db *sql.DB, searchClient *search.Client, rdb *redis.Cl
audio.GET("/modelList", modelList.ModelList)
audio.GET("/reportDevInfo", device.ReportDevInfo)
audio.GET("/ota", ota.GetOTA)
audio.GET("/getCurve", curve.GetCurve)
}
return r