From 7687e6a0d94a08f0e7ff6b7497dd632844e3790d Mon Sep 17 00:00:00 2001 From: eafonyang Date: Wed, 17 Jun 2026 13:52:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=86=E4=BA=AB=E7=A0=81=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20model=20=E5=AD=97=E6=AE=B5=EF=BC=8C?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E6=93=8D=E4=BD=9C=E7=9A=84=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E7=9A=84=E5=9E=8B=E5=8F=B7=20X8/X9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + docs/docs.go | 102 +++++----- docs/swagger.json | 102 +++++----- docs/swagger.yaml | 72 +++---- internal/cache/share_code_cache.go | 26 ++- internal/handler/share_code.go | 35 +++- internal/model/share_code.go | 10 + internal/repository/share_code.go | 6 +- internal/task/share_code_persist.go | 2 + olds/EqualizeController.java | 154 --------------- olds/ModelService.java | 280 ---------------------------- sql/share_code_log.sql | 1 + 12 files changed, 214 insertions(+), 577 deletions(-) delete mode 100644 olds/EqualizeController.java delete mode 100644 olds/ModelService.java diff --git a/.gitignore b/.gitignore index 4b52cb3..e952fa8 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ vendor/ # IDE .idea/ .vscode/ +.qoder/ *.swp *.swo diff --git a/docs/docs.go b/docs/docs.go index f06c0b5..896d93a 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -464,50 +464,6 @@ const docTemplate = `{ } } }, - "/audio/shareQuery": { - "get": { - "description": "根据分享码预览 EQ 数据,不写入导入流水", - "produces": [ - "application/json" - ], - "tags": [ - "ShareCode" - ], - "summary": "查询 EQ 分享码", - "parameters": [ - { - "type": "string", - "description": "5 位分享码", - "name": "shareCode", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "成功返回 eq_data、expire_at", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "400": { - "description": "参数校验失败", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "500": { - "description": "系统错误", - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - } - }, "/audio/shareAccept": { "get": { "description": "根据分享码获取他人分享的 EQ 数据", @@ -526,6 +482,13 @@ const docTemplate = `{ "in": "query", "required": true }, + { + "type": "string", + "description": "操作设备型号,取值 Luxsin-X9 或 Luxsin-X8", + "name": "model", + "in": "query", + "required": true + }, { "type": "string", "description": "5 位分享码", @@ -561,7 +524,7 @@ const docTemplate = `{ }, "/audio/shareCreate": { "post": { - "description": "将用户的 EQ 数据生成一个 5 位分享码,有效期 30 分钟", + "description": "将用户的 EQ 数据生成一个 5 位分享码,有效期由 SHARE_CODE_TTL_MIN 配置决定(支持 30、30m、24h、30d 等格式)", "consumes": [ "application/json" ], @@ -574,7 +537,7 @@ const docTemplate = `{ "summary": "创建 EQ 分享码", "parameters": [ { - "description": "分享请求", + "description": "分享请求,model 取值 Luxsin-X9 或 Luxsin-X8", "name": "body", "in": "body", "required": true, @@ -586,6 +549,9 @@ const docTemplate = `{ }, "mac": { "type": "string" + }, + "model": { + "type": "string" } } } @@ -659,6 +625,50 @@ const docTemplate = `{ } } } + }, + "/audio/shareQuery": { + "get": { + "description": "根据分享码预览 EQ 数据,不写入导入流水", + "produces": [ + "application/json" + ], + "tags": [ + "ShareCode" + ], + "summary": "查询 EQ 分享码", + "parameters": [ + { + "type": "string", + "description": "5 位分享码", + "name": "shareCode", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "成功返回 eq_data、expire_at", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "参数校验失败", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "系统错误", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } } } }` diff --git a/docs/swagger.json b/docs/swagger.json index effe6fb..60fdafe 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -457,50 +457,6 @@ } } }, - "/audio/shareQuery": { - "get": { - "description": "根据分享码预览 EQ 数据,不写入导入流水", - "produces": [ - "application/json" - ], - "tags": [ - "ShareCode" - ], - "summary": "查询 EQ 分享码", - "parameters": [ - { - "type": "string", - "description": "5 位分享码", - "name": "shareCode", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "成功返回 eq_data、expire_at", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "400": { - "description": "参数校验失败", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "500": { - "description": "系统错误", - "schema": { - "type": "object", - "additionalProperties": true - } - } - } - } - }, "/audio/shareAccept": { "get": { "description": "根据分享码获取他人分享的 EQ 数据", @@ -519,6 +475,13 @@ "in": "query", "required": true }, + { + "type": "string", + "description": "操作设备型号,取值 Luxsin-X9 或 Luxsin-X8", + "name": "model", + "in": "query", + "required": true + }, { "type": "string", "description": "5 位分享码", @@ -554,7 +517,7 @@ }, "/audio/shareCreate": { "post": { - "description": "将用户的 EQ 数据生成一个 5 位分享码,有效期 30 分钟", + "description": "将用户的 EQ 数据生成一个 5 位分享码,有效期由 SHARE_CODE_TTL_MIN 配置决定(支持 30、30m、24h、30d 等格式)", "consumes": [ "application/json" ], @@ -567,7 +530,7 @@ "summary": "创建 EQ 分享码", "parameters": [ { - "description": "分享请求", + "description": "分享请求,model 取值 Luxsin-X9 或 Luxsin-X8", "name": "body", "in": "body", "required": true, @@ -579,6 +542,9 @@ }, "mac": { "type": "string" + }, + "model": { + "type": "string" } } } @@ -652,6 +618,50 @@ } } } + }, + "/audio/shareQuery": { + "get": { + "description": "根据分享码预览 EQ 数据,不写入导入流水", + "produces": [ + "application/json" + ], + "tags": [ + "ShareCode" + ], + "summary": "查询 EQ 分享码", + "parameters": [ + { + "type": "string", + "description": "5 位分享码", + "name": "shareCode", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "成功返回 eq_data、expire_at", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "参数校验失败", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "500": { + "description": "系统错误", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } } } } \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 6bab092..dc79703 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -303,36 +303,6 @@ paths: summary: 上报设备信息 tags: - Device - /audio/shareQuery: - get: - description: 根据分享码预览 EQ 数据,不写入导入流水 - parameters: - - description: 5 位分享码 - in: query - name: shareCode - required: true - type: string - produces: - - application/json - responses: - "200": - description: 成功返回 eq_data、expire_at - schema: - additionalProperties: true - type: object - "400": - description: 参数校验失败 - schema: - additionalProperties: true - type: object - "500": - description: 系统错误 - schema: - additionalProperties: true - type: object - summary: 查询 EQ 分享码 - tags: - - ShareCode /audio/shareAccept: get: description: 根据分享码获取他人分享的 EQ 数据 @@ -342,6 +312,11 @@ paths: name: mac required: true type: string + - description: 操作设备型号,取值 Luxsin-X9 或 Luxsin-X8 + in: query + name: model + required: true + type: string - description: 5 位分享码 in: query name: shareCode @@ -372,9 +347,10 @@ paths: post: consumes: - application/json - description: 将用户的 EQ 数据生成一个 5 位分享码,有效期 30 分钟 + description: 将用户的 EQ 数据生成一个 5 位分享码,有效期由 SHARE_CODE_TTL_MIN 配置决定(支持 30、30m、24h、30d + 等格式) parameters: - - description: 分享请求 + - description: 分享请求,model 取值 Luxsin-X9 或 Luxsin-X8 in: body name: body required: true @@ -384,6 +360,8 @@ paths: type: object mac: type: string + model: + type: string type: object produces: - application/json @@ -436,4 +414,34 @@ paths: summary: 查询未过期分享码 tags: - ShareCode + /audio/shareQuery: + get: + description: 根据分享码预览 EQ 数据,不写入导入流水 + parameters: + - description: 5 位分享码 + in: query + name: shareCode + required: true + type: string + produces: + - application/json + responses: + "200": + description: 成功返回 eq_data、expire_at + schema: + additionalProperties: true + type: object + "400": + description: 参数校验失败 + schema: + additionalProperties: true + type: object + "500": + description: 系统错误 + schema: + additionalProperties: true + type: object + summary: 查询 EQ 分享码 + tags: + - ShareCode swagger: "2.0" diff --git a/internal/cache/share_code_cache.go b/internal/cache/share_code_cache.go index 7247cf3..0a3f662 100644 --- a/internal/cache/share_code_cache.go +++ b/internal/cache/share_code_cache.go @@ -5,6 +5,7 @@ // share:{code} Hash, TTL = SHARE_CODE_TTL_MIN — 分享码主数据 // mac_addr 创建者 MAC 地址 // ip_addr 创建者 IP +// model 操作设备型号 // eq_data EQ 参数 JSON // expire_at 过期时间 (RFC3339) // persisted 是否已刷入 DB ("0"/"1") @@ -57,6 +58,7 @@ const ( fieldMacAddr = "mac_addr" fieldIPAddr = "ip_addr" + fieldModel = "model" fieldEqData = "eq_data" fieldExpireAt = "expire_at" fieldPersisted = "persisted" @@ -69,21 +71,23 @@ end redis.call('HSET', KEYS[1], 'mac_addr', ARGV[1], 'ip_addr', ARGV[2], - 'eq_data', ARGV[3], - 'expire_at', ARGV[4], + 'model', ARGV[3], + 'eq_data', ARGV[4], + 'expire_at', ARGV[5], 'persisted', '0' ) -redis.call('EXPIRE', KEYS[1], tonumber(ARGV[5])) -redis.call('SADD', KEYS[2], ARGV[6]) +redis.call('EXPIRE', KEYS[1], tonumber(ARGV[6])) +redis.call('SADD', KEYS[2], ARGV[7]) -- ZSET: score = expire_at unix timestamp, member = share code -redis.call('ZADD', KEYS[3], tonumber(ARGV[7]), ARGV[6]) -redis.call('EXPIRE', KEYS[3], tonumber(ARGV[8])) +redis.call('ZADD', KEYS[3], tonumber(ARGV[8]), ARGV[7]) +redis.call('EXPIRE', KEYS[3], tonumber(ARGV[9])) return 1 `) type ShareImportPendingLog struct { MacAddr string `json:"mac_addr"` ShareCode string `json:"share_code"` + Model string `json:"model"` IpAddr string `json:"ip_addr"` EqData string `json:"eq_data"` ExpireAt string `json:"expire_at"` @@ -93,6 +97,7 @@ type ShareCodeData struct { ShareCode string MacAddr string IpAddr string + Model string EqData string ExpireAt time.Time Persisted bool @@ -112,7 +117,7 @@ func (c *ShareCodeCache) ShareCodeTTL() time.Duration { return c.codeTTL } -func (c *ShareCodeCache) Create(ctx context.Context, macAddr, ipAddr string, eqData []byte) (*ShareCodeData, error) { +func (c *ShareCodeCache) Create(ctx context.Context, macAddr, ipAddr, model string, eqData []byte) (*ShareCodeData, error) { expireAt := time.Now().Add(c.codeTTL) eqJSON := string(eqData) @@ -125,7 +130,7 @@ func (c *ShareCodeCache) Create(ctx context.Context, macAddr, ipAddr string, eqD key := shareCodeKey(code) macIdxKey := shareMacIndexKey(macAddr) ok, err := shareCreateScript.Run(ctx, c.rdb, []string{key, sharePendingSet, macIdxKey}, - macAddr, ipAddr, eqJSON, expireAt.Format(time.RFC3339), int(c.codeTTL.Seconds()), code, expireAt.Unix(), int(c.codeTTL.Seconds()), + macAddr, ipAddr, model, eqJSON, expireAt.Format(time.RFC3339), int(c.codeTTL.Seconds()), code, expireAt.Unix(), int(c.codeTTL.Seconds()), ).Int() if err != nil { return nil, fmt.Errorf("create share code in redis: %w", err) @@ -135,6 +140,7 @@ func (c *ShareCodeCache) Create(ctx context.Context, macAddr, ipAddr string, eqD ShareCode: code, MacAddr: macAddr, IpAddr: ipAddr, + Model: model, EqData: eqJSON, ExpireAt: expireAt, Persisted: false, @@ -164,6 +170,7 @@ func (c *ShareCodeCache) Get(ctx context.Context, shareCode string) (*ShareCodeD ShareCode: shareCode, MacAddr: values[fieldMacAddr], IpAddr: values[fieldIPAddr], + Model: values[fieldModel], EqData: values[fieldEqData], ExpireAt: expireAt, Persisted: values[fieldPersisted] == "1", @@ -208,11 +215,12 @@ func (c *ShareCodeCache) MarkPersisted(ctx context.Context, shareCode string) er return c.rdb.SRem(ctx, sharePendingSet, shareCode).Err() } -func (c *ShareCodeCache) EnqueueImportLog(ctx context.Context, macAddr, shareCode, ipAddr, eqData string, expireAt time.Time) error { +func (c *ShareCodeCache) EnqueueImportLog(ctx context.Context, macAddr, shareCode, model, ipAddr, eqData string, expireAt time.Time) error { field := fmt.Sprintf("%s:%s", macAddr, shareCode) // 同 MAC+code 幂等,避免重复导入产生多条记录 payload, err := json.Marshal(ShareImportPendingLog{ MacAddr: macAddr, ShareCode: shareCode, + Model: model, IpAddr: ipAddr, EqData: eqData, ExpireAt: expireAt.Format(time.RFC3339), diff --git a/internal/handler/share_code.go b/internal/handler/share_code.go index cc678bc..c3acb31 100644 --- a/internal/handler/share_code.go +++ b/internal/handler/share_code.go @@ -7,6 +7,7 @@ import ( "github.com/gin-gonic/gin" "github.com/luxsin/app-api/internal/cache" + "github.com/luxsin/app-api/internal/model" "github.com/luxsin/app-api/pkg/encode" "go.uber.org/zap" ) @@ -28,17 +29,18 @@ func NewShareCodeHandler(shareCache *cache.ShareCodeCache, log *zap.Logger, maxP // @Tags ShareCode // @Accept json // @Produce json -// @Param body body object{mac=string,eq_data=object} true "分享请求" +// @Param body body object{mac=string,model=string,eq_data=object} true "分享请求,model 取值 Luxsin-X9 或 Luxsin-X8" // @Success 200 {object} map[string]any "成功返回 share_code、expire_at、eq_data" // @Failure 400 {object} map[string]any "参数校验失败" // @Failure 500 {object} map[string]any "系统错误" // @Router /audio/shareCreate [post] // // POST /audio/shareCreate -// Body: {"mac": "xx", "eq_data": {...}} +// Body: {"mac": "xx", "model": "Luxsin-X9", "eq_data": {...}} func (h *ShareCodeHandler) ExportShareCode(c *gin.Context) { var req struct { Mac string `json:"mac"` + Model string `json:"model"` EqData json.RawMessage `json:"eq_data"` } if err := c.ShouldBindJSON(&req); err != nil { @@ -50,10 +52,11 @@ func (h *ShareCodeHandler) ExportShareCode(c *gin.Context) { } mac := strings.TrimSpace(req.Mac) + deviceModel := strings.TrimSpace(req.Model) eqDataRaw := strings.TrimSpace(string(req.EqData)) clientIP := encode.ClientPublicIP(c) - if mac == "" || eqDataRaw == "" { + if mac == "" || deviceModel == "" || eqDataRaw == "" { c.JSON(http.StatusOK, gin.H{ "code": 400, "msg": "参数校验失败", @@ -61,6 +64,14 @@ func (h *ShareCodeHandler) ExportShareCode(c *gin.Context) { return } + if !model.IsValidShareDeviceModel(deviceModel) { + c.JSON(http.StatusOK, gin.H{ + "code": 400, + "msg": "model 参数无效", + }) + return + } + if !json.Valid([]byte(eqDataRaw)) { c.JSON(http.StatusOK, gin.H{ "code": 400, @@ -89,7 +100,7 @@ func (h *ShareCodeHandler) ExportShareCode(c *gin.Context) { return } - data, err := h.cache.Create(ctx, mac, clientIP, []byte(eqDataRaw)) + data, err := h.cache.Create(ctx, mac, clientIP, deviceModel, []byte(eqDataRaw)) if err != nil { h.log.Error("create share code failed", zap.Error(err)) c.JSON(http.StatusOK, gin.H{ @@ -205,19 +216,21 @@ func (h *ShareCodeHandler) QueryShareCode(c *gin.Context) { // @Tags ShareCode // @Produce json // @Param mac query string true "设备 MAC 地址" +// @Param model query string true "操作设备型号,取值 Luxsin-X9 或 Luxsin-X8" // @Param shareCode query string true "5 位分享码" // @Success 200 {object} map[string]any "成功返回 eq_data" // @Failure 400 {object} map[string]any "参数校验失败" // @Failure 500 {object} map[string]any "系统错误" // @Router /audio/shareAccept [get] // -// GET /audio/shareAccept?mac=xx&shareCode=ABC12 +// GET /audio/shareAccept?mac=xx&model=Luxsin-X9&shareCode=ABC12 func (h *ShareCodeHandler) ImportShareCode(c *gin.Context) { mac := strings.TrimSpace(c.Query("mac")) + deviceModel := strings.TrimSpace(c.Query("model")) shareCode := strings.TrimSpace(c.Query("shareCode")) clientIP := encode.ClientPublicIP(c) - if mac == "" || shareCode == "" { + if mac == "" || deviceModel == "" || shareCode == "" { c.JSON(http.StatusOK, gin.H{ "code": 400, "msg": "参数校验失败", @@ -225,13 +238,21 @@ func (h *ShareCodeHandler) ImportShareCode(c *gin.Context) { return } + if !model.IsValidShareDeviceModel(deviceModel) { + c.JSON(http.StatusOK, gin.H{ + "code": 400, + "msg": "model 参数无效", + }) + return + } + data, ok := h.lookupShareCode(c, shareCode) if !ok { return } ctx := c.Request.Context() - if err := h.cache.EnqueueImportLog(ctx, mac, shareCode, clientIP, data.EqData, data.ExpireAt); err != nil { + if err := h.cache.EnqueueImportLog(ctx, mac, shareCode, deviceModel, clientIP, data.EqData, data.ExpireAt); err != nil { h.log.Error("enqueue share import log failed", zap.String("share_code", shareCode), zap.Error(err), diff --git a/internal/model/share_code.go b/internal/model/share_code.go index 2dd63ce..83507a2 100644 --- a/internal/model/share_code.go +++ b/internal/model/share_code.go @@ -2,11 +2,21 @@ package model import "time" +const ( + ShareDeviceModelX9 = "Luxsin-X9" + ShareDeviceModelX8 = "Luxsin-X8" +) + +func IsValidShareDeviceModel(model string) bool { + return model == ShareDeviceModelX9 || model == ShareDeviceModelX8 +} + type ShareCodeLog struct { ID int MacAddr string ShareCode string Action string // export | import + Model string IpAddr string EqData []byte ExpireAt *time.Time diff --git a/internal/repository/share_code.go b/internal/repository/share_code.go index 72a2ded..e40bebb 100644 --- a/internal/repository/share_code.go +++ b/internal/repository/share_code.go @@ -18,8 +18,8 @@ func NewShareCodeRepository(db *sql.DB) *ShareCodeRepository { } func (r *ShareCodeRepository) InsertLog(ctx context.Context, log model.ShareCodeLog) error { - const query = `INSERT INTO share_code_log (mac_addr, share_code, action, ip_addr, eq_data, expire_at) - VALUES (?, ?, ?, ?, ?, ?)` + const query = `INSERT INTO share_code_log (mac_addr, share_code, action, model, ip_addr, eq_data, expire_at) + VALUES (?, ?, ?, ?, ?, ?, ?)` var expireAt any if log.ExpireAt != nil { @@ -27,7 +27,7 @@ func (r *ShareCodeRepository) InsertLog(ctx context.Context, log model.ShareCode } _, err := r.db.ExecContext(ctx, query, - log.MacAddr, log.ShareCode, log.Action, log.IpAddr, log.EqData, expireAt, + log.MacAddr, log.ShareCode, log.Action, log.Model, log.IpAddr, log.EqData, expireAt, ) if err != nil { return fmt.Errorf("insert share_code_log: %w", err) diff --git a/internal/task/share_code_persist.go b/internal/task/share_code_persist.go index 74e2595..1361aa0 100644 --- a/internal/task/share_code_persist.go +++ b/internal/task/share_code_persist.go @@ -158,6 +158,7 @@ func (t *ShareCodePersistTask) persistExportOne(ctx context.Context, shareCode s MacAddr: data.MacAddr, ShareCode: data.ShareCode, Action: "export", + Model: data.Model, IpAddr: data.IpAddr, EqData: []byte(data.EqData), ExpireAt: repository.ParseShareExpireAt(data.ExpireAt), @@ -207,6 +208,7 @@ func (t *ShareCodePersistTask) persistImportOne(ctx context.Context, field, payl MacAddr: pending.MacAddr, ShareCode: pending.ShareCode, Action: "import", + Model: pending.Model, IpAddr: pending.IpAddr, EqData: []byte(pending.EqData), ExpireAt: repository.ParseShareExpireAt(expireAt), diff --git a/olds/EqualizeController.java b/olds/EqualizeController.java deleted file mode 100644 index ecc5561..0000000 --- a/olds/EqualizeController.java +++ /dev/null @@ -1,154 +0,0 @@ -package com.luxsin.app.controller; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; -import com.luxsin.app.annotation.Base64Response; -import com.luxsin.app.bean.OTA; -import com.luxsin.app.service.BrandService; -import com.luxsin.app.service.ModelService; -import com.luxsin.app.service.OTAService; -import lombok.extern.slf4j.Slf4j; -import org.eafon.data.EResponseTool; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.web.bind.annotation.*; -import java.time.LocalDate; -import java.util.Map; - -@Slf4j -@RestController -@CrossOrigin(origins = "*") -public class EqualizeController { - private final ModelService modelService; - - private final OTAService otaService; - - private final RedisTemplate redisTemplate; - - private final BrandService brandService; - public EqualizeController(ModelService modelService, OTAService otaService, RedisTemplate redisTemplate, BrandService brandService) { - this.modelService = modelService; - this.otaService = otaService; - this.redisTemplate = redisTemplate; - this.brandService = brandService; - } - - /** - * 耳机列表 - * @param key - * @param count - * @return - */ - @GetMapping("modelList") - @Base64Response - public String getModelList(String key, @RequestParam(value = "count", defaultValue = "100") int count){ - return modelService.modelList(key, count); - } - - /** - * 目标曲线 - * @param brand - * @param name - * @param target - * @return - */ - @GetMapping("getCurve") - @Base64Response - public String getCurve(String brand, String name, String target, @RequestParam(value = "includeRaw", defaultValue = "false") boolean includeRaw){ - String result = modelService.getCurvePoint(brand, name, target); - if(!includeRaw){ - JSONObject resp = JSONUtil.parseObj(result); - if(resp.containsKey("code") && resp.getInt("code") != 0){ - resp.remove("fr"); - } - return resp.toString(); - } - return result; - } - - @GetMapping("modelCurve") - @Base64Response - public String modelCurve(String brand, String name){ - return modelService.getModelCurve(brand, name); - } - - @GetMapping("test/modelCurve") - public String testModelCurve(String brand, String name){ - return modelService.getModelCurve(brand, name); - } - - @GetMapping("test/getCurve") - public String testGetCurve(String brand, String name, String target, - @RequestParam(value = "includeRaw", defaultValue = "false") boolean includeRaw){ - String result = modelService.getCurvePoint(brand, name, target); - if(!includeRaw){ - JSONObject resp = JSONUtil.parseObj(result); - if(resp.containsKey("code") && resp.getInt("code") != 0){ - resp.remove("fr"); - } - return resp.toString(); - } - return result; - } - - /** - * 升级 - * @param model - * @param hw - * @param mac - * @param beta - * @return - */ - @GetMapping("ota") - public EResponseTool OTA(String model, String hw, @RequestParam(value = "mac", defaultValue = "null") String mac, - @RequestParam(value = "beta", defaultValue = "0") int beta){ - return otaService.getOTA(model, hw, mac, beta); - } - - /** - * 上报用户设备信息 - * @param mac - * @param model - * @return - */ - @GetMapping("reportDevInfo") - public EResponseTool reportDevInfo(String mac, String model,String ver, - @RequestHeader(value = "X-Forwarded-For", required = false) String xForwardedFor){ - if(StrUtil.isEmpty(mac) || StrUtil.isEmpty(model)){ - return EResponseTool.error(EResponseTool.ResultCode.VALIDATE_FAILED); - } - log.info("report dev info remote ip = {}, now time = {}", xForwardedFor, DateUtil.now()); - ver = null == ver ? "" : ver; - xForwardedFor = null == xForwardedFor ? "" : xForwardedFor; - - redisTemplate.opsForHash().put("devices", mac, JSONUtil.toJsonStr(Map.of("mac_addr", mac, "model", model, - "active_date", LocalDate.now().toString(), - "ip_addr", xForwardedFor, - "ver", ver))); - return EResponseTool.success(); - } - - /** - * 获取品牌列表 - * @param brandName - * @return - */ - @GetMapping("getBrand") - @Base64Response - public String getBrand(String brandName){ - return brandService.brandList(brandName); - } - - /** - * 获取型号列表 - * @param brandName - * @param modelName - * @return - */ - @GetMapping("getModel") - @Base64Response - public String getModel(String brandName, String modelName){ - return modelService.getModels(brandName, modelName); - } -} diff --git a/olds/ModelService.java b/olds/ModelService.java deleted file mode 100644 index 8c136ad..0000000 --- a/olds/ModelService.java +++ /dev/null @@ -1,280 +0,0 @@ -package com.luxsin.app.service; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.NumberUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpUtil; -import cn.hutool.json.JSONArray; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; -import com.luxsin.app.bean.Model; -import com.luxsin.app.bean.Target; -import com.luxsin.app.bean.table.ModelTableDef; -import com.luxsin.app.bean.table.TargetTableDef; -import com.luxsin.app.config.MyConfig; -import com.luxsin.app.dao.ModelMapper; -import com.luxsin.app.dao.TargetMapper; -import com.meilisearch.sdk.Index; -import com.meilisearch.sdk.SearchRequest; -import com.meilisearch.sdk.model.Searchable; -import com.mybatisflex.core.query.QueryWrapper; -import com.mybatisflex.spring.service.impl.ServiceImpl; -import lombok.extern.slf4j.Slf4j; -import org.eafon.data.EResponseTool; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -@Slf4j -@Service -public class ModelService extends ServiceImpl { - private final RedisTemplate redisTemplate; - - private final TargetMapper targetMapper; - - private final MyConfig myConfig; - - private final Index modelIdx; - - public ModelService(RedisTemplate redisTemplate, TargetMapper targetMapper, - MyConfig myConfig, Index modelIdx) { - this.redisTemplate = redisTemplate; - this.targetMapper = targetMapper; - this.myConfig = myConfig; - this.modelIdx = modelIdx; - } - - /** - * 获取手机列表 - * @param key - * @param count - * @return - */ - public String modelList(String key, int count){ - List list = Collections.emptyList(); - Searchable searchable = modelIdx.search(SearchRequest.builder() - .q(key) - .attributesToRetrieve(new String[]{"rig", "form", "name", "brand_name", "source", "eq_key"}) - .limit(count) - .build()); - if(!searchable.getHits().isEmpty()){ - return JSONUtil.toJsonStr(searchable.getHits()); - }else { - return JSONUtil.toJsonStr(list); - } - } - - /** - * 获取目标曲线 - * @param brand - * @param name - * @param targetName - * @param raw 是否只是需要机型的raw数据 - * @return - */ - public String getCurvePoint(String brand, String name, String targetName){ - if(StrUtil.isEmpty(brand) || StrUtil.isEmpty(name) || StrUtil.isEmpty(targetName)){ - return EResponseTool.error(EResponseTool.ResultCode.VALIDATE_FAILED).toString(); - } - String cacheKey = brand + " " + name; - String cache = String.valueOf(redisTemplate.opsForHash().get(cacheKey, targetName)); - if(StrUtil.isEmpty(cache) || "null".equals(cache)){ - String lockKey = cacheKey+":"+targetName+":lock"; - boolean locked = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS)); - if(locked){ - try { - cache = String.valueOf(redisTemplate.opsForHash().get(cacheKey, targetName)); - if(StrUtil.isEmpty(cache) || "null".equals(cache)){ - //redis中没有数据的,需要请求eq接口 - JSONObject resp = getCurvePointFromPEQ(brand, name, targetName); - if(null!=resp && resp.getInt("code") == EResponseTool.ResultCode.SUCCESS.getCode()){ - //将请求接口得到的数据,放入缓存 - redisTemplate.opsForHash().put(brand+" "+name, targetName, resp.toString()); - return resp.toString(); - } - } - }finally { - redisTemplate.delete(lockKey); - } - }else{ - try { - TimeUnit.MILLISECONDS.sleep(100); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - return getCurvePoint(brand, name, targetName); - } - }else{ - log.info("brand: {} name: {} target {} read cache.", brand, name, targetName); - return cache; - } - - return EResponseTool.error(EResponseTool.ResultCode.EMPTY).toString(); - } - - private JSONObject getCurvePointFromPEQ(String brand, String name, String targetName){ - Model model = getOne(QueryWrapper.create().where(ModelTableDef.MODEL.BRAND_NAME.eq(brand)). - and(ModelTableDef.MODEL.NAME.eq(name))); - if(!Objects.isNull(model)){ - Target target = targetMapper.selectOneByQuery(QueryWrapper.create().where(TargetTableDef.TARGET.LABEL.eq(targetName))); - if(!Objects.isNull(target)){ - String eq_key = model.getEq_key(); - String headPhone = brand+" "+name; - if(StrUtil.isNotEmpty(eq_key) && eq_key.equals("name")){ - headPhone = name; - } - - JSONObject measurement = null; - if("Eafonyoung".equals(model.getSource())){ - measurement = getCSV(myConfig.getMeasurementBasePath()+"/Eafonyoung/data/"+model.getForm()+"/"+headPhone+".csv"); - if(Objects.isNull(measurement)){ - return null; - } - } - - JSONObject resp; - if(target.isRead_csv()){ - //读取自定义的target - JSONObject targetRaw = getCSV(myConfig.getTargetBasePath()+"/"+target.getFile()); - resp = reqEqualize(headPhone, measurement, targetRaw, JSONUtil.parseObj(target.getBassBoost()), - model.getSource(), model.getRig()); - }else{ - resp = reqEqualize(headPhone, measurement, targetName, JSONUtil.parseObj(target.getBassBoost()), - model.getSource(), model.getRig()); - } - - if(resp.getInt("code") == EResponseTool.ResultCode.SUCCESS.getCode()){ - return resp; - }else{ - log.info("请求reqEqualize接口失败,没有数据"); - } - } - } - return null; - } - - - private JSONObject getCSV(String path){ - if(FileUtil.exist(path)){ - List list = FileUtil.readLines(path, "UTF-8"); - JSONArray frequency = new JSONArray(); - JSONArray raw = new JSONArray(); - for(int i=1;i reqMap = new HashMap<>(); - reqMap.put("target", target); - reqMap.put("sound_signature", null); - reqMap.put("sound_signature_smoothing_window_size", 1); - reqMap.put("bass_boost_gain", bassBoost.getFloat("gain")); - reqMap.put("bass_boost_fc", bassBoost.getInt("fc")); - reqMap.put("bass_boost_q", bassBoost.getFloat("q")); - reqMap.put("treble_boost_gain",0); - reqMap.put("treble_boost_fc", 10000); - reqMap.put("treble_boost_q", 0.7); - reqMap.put("tilt", 0); - reqMap.put("fs",48000); - reqMap.put("bit_depth", 16); - reqMap.put("phase", "minimum"); - reqMap.put("f_res", 16); - reqMap.put("preamp",0); - reqMap.put("max_gain" ,12); - reqMap.put("max_slope", 18); - reqMap.put("window_size", 0.08); - reqMap.put("treble_window_size", 2); - reqMap.put("treble_f_lower", 6000); - reqMap.put("treble_f_upper", 8000); - reqMap.put("treble_gain_k", 1); - reqMap.put("graphic_eq", false); - reqMap.put("parametric_eq", true); - reqMap.put("fixed_band_eq", false); - reqMap.put("convolution_eq", false); - - - if(null!=measurement){ - reqMap.put("measurement", measurement); - } - else if(StrUtil.isNotEmpty(headPhone)){ - reqMap.put("name", headPhone); - } - - reqMap.put("source", source); - reqMap.put("rig", rig); - reqMap.put("parametric_eq_config", "MINIDSP_IL_DSP"); - - Map response = new HashMap<>(); - JSONArray fr_fields = new JSONArray(); - fr_fields.addAll(List.of("raw")); - response.put("fr_f_step", 1.02); - response.put("base64fp16" ,false); - response.put("fr_fields", fr_fields); - - reqMap.put("response", response); - - String resp = HttpUtil.post("https://autoeq.app/equalize", new JSONObject(reqMap).toString(), 10000); - JSONObject result = new JSONObject(); - if(StrUtil.isNotEmpty(resp)){ - JSONObject respJson = JSONUtil.parseObj(resp); - JSONObject parametric_eq = respJson.getJSONObject("parametric_eq"); - if(Objects.isNull(parametric_eq)){ - result.putOnce("code", 0); - result.putOnce("msg", "req param error"); - result.putOnce("param", reqMap); - return result; - }else { - result.putOnce("parametric_eq", respJson.getJSONObject("parametric_eq")); - result.putOnce("fr", respJson.getJSONObject("fr")); - } - } - result.putOnce("code", EResponseTool.ResultCode.SUCCESS.getCode()); - result.putOnce("msg", "ok"); - return result; - } - - public JSONObject reqEqualize(String headPhone, JSONObject measurement, String target, JSONObject bassBoost, String source, String rig) { - return reqEqualizeInternal(headPhone, measurement, target, bassBoost, source, rig); - } - - public JSONObject reqEqualize(String headPhone, JSONObject measurement, JSONObject target, JSONObject bassBoost, String source, String rig) { - return reqEqualizeInternal(headPhone, measurement, target, bassBoost, source, rig); - } - - public String getModels(String brandName, String modelName){ - List list; - if(StrUtil.isNotEmpty(brandName)){ - list = list(QueryWrapper.create().where(ModelTableDef.MODEL.BRAND_NAME.eq(brandName)).orderBy(ModelTableDef.MODEL.NAME, true)); - } - else if(StrUtil.isNotEmpty(modelName)){ - list = list(QueryWrapper.create().where(ModelTableDef.MODEL.NAME.like(modelName)).orderBy(ModelTableDef.MODEL.NAME, true)); - } - else{ - list = Collections.emptyList(); - } - return JSONUtil.toJsonStr(list); - } - - public String getModelCurve(String brand, String name){ - return getCurvePoint(brand, name, "Harman over-ear 2018"); - } -} diff --git a/sql/share_code_log.sql b/sql/share_code_log.sql index 094fb23..e0803ab 100644 --- a/sql/share_code_log.sql +++ b/sql/share_code_log.sql @@ -11,6 +11,7 @@ CREATE TABLE `share_code_log` ( `mac_addr` varchar(17) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '设备 MAC 地址,如 AA:BB:CC:DD:EE:FF', `share_code` char(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '分享码(5位字符)', `action` enum('export','import') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '操作类型:export=导出分享码,import=导入分享码', + `model` enum('Luxsin-X9','Luxsin-X8') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '操作设备:是在哪一台设备上做的导出/导入操作', `ip_addr` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '用户 IP 地址(支持 IPv4/IPv6)', `eq_data` json NOT NULL COMMENT 'EQ 数据快照(JSON)', `expire_at` datetime NULL DEFAULT NULL COMMENT '分享码到期时间(导出时快照,导入可为空)',