fix(platform): guard unscoped raw field queries

This commit is contained in:
lingniu
2026-07-04 12:45:53 +08:00
parent 773338cce9
commit 34116142e6
3 changed files with 77 additions and 0 deletions

View File

@@ -186,6 +186,10 @@ func (h *Handler) handleOpsHealth(w http.ResponseWriter, r *http.Request) {
func (h *Handler) write(w http.ResponseWriter, r *http.Request, data any, err error) {
if err != nil {
if clientErr, ok := asClientError(err); ok {
httpx.WriteError(w, http.StatusBadRequest, clientErr.Code, clientErr.Message, "", traceID(r))
return
}
httpx.WriteError(w, http.StatusInternalServerError, "INTERNAL", "服务处理失败", err.Error(), traceID(r))
return
}

View File

@@ -941,6 +941,47 @@ func TestHandlerRawFramesPostAcceptsKeyword(t *testing.T) {
}
}
func TestHandlerRawFramesRejectsUnscopedIncludeFields(t *testing.T) {
handler := NewHandler(NewService(NewMockStore()))
cases := []struct {
name string
req *http.Request
}{
{
name: "get",
req: httptest.NewRequest(http.MethodGet, "/api/history/raw-frames?limit=1&includeFields=true", nil),
},
{
name: "post",
req: httptest.NewRequest(http.MethodPost, "/api/history/raw-frames/query", strings.NewReader(`{"limit":1,"includeFields":true}`)),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, tc.req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
}
var body struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
t.Fatalf("response JSON should decode: %v body=%s", err, rec.Body.String())
}
if body.Error.Code != "RAW_FRAME_SCOPE_REQUIRED" {
t.Fatalf("error code = %q body=%s", body.Error.Code, rec.Body.String())
}
if !strings.Contains(body.Error.Message, "车辆或时间范围") {
t.Fatalf("error message should explain required scope: %s", rec.Body.String())
}
})
}
}
func TestHandlerRawFramesKeepsUnresolvedKeywordEmpty(t *testing.T) {
handler := NewHandler(NewService(NewMockStore()))
rec := httptest.NewRecorder()

View File

@@ -2,6 +2,7 @@ package platform
import (
"context"
"errors"
"math"
"net/url"
"sort"
@@ -56,6 +57,23 @@ type Service struct {
runtime RuntimeInfo
}
type clientError struct {
Code string
Message string
}
func (e clientError) Error() string {
return e.Message
}
func asClientError(err error) (clientError, bool) {
var target clientError
if errors.As(err, &target) {
return target, true
}
return clientError{}, false
}
var canonicalVehicleProtocols = []string{"GB32960", "JT808", "YUTONG_MQTT"}
func completeProtocolStats(stats []ProtocolStat) []ProtocolStat {
@@ -737,6 +755,12 @@ func (s *Service) RawFrames(ctx context.Context, query RawFrameQuery) (Page[RawF
if query.Limit <= 0 || query.Limit > 500 {
query.Limit = 100
}
if query.IncludeFields && !rawFrameQueryHasNarrowScope(query) {
return Page[RawFrameRow]{}, clientError{
Code: "RAW_FRAME_SCOPE_REQUIRED",
Message: "RAW 解析字段查询需要提供车辆或时间范围",
}
}
if strings.TrimSpace(query.VIN) == "" {
query.VIN = query.Keyword
}
@@ -750,6 +774,14 @@ func (s *Service) RawFrames(ctx context.Context, query RawFrameQuery) (Page[RawF
return s.store.RawFrames(ctx, query)
}
func rawFrameQueryHasNarrowScope(query RawFrameQuery) bool {
return strings.TrimSpace(query.VIN) != "" ||
strings.TrimSpace(query.Keyword) != "" ||
strings.TrimSpace(query.DateFrom) != "" ||
strings.TrimSpace(query.DateTo) != "" ||
len(query.Fields) > 0
}
func (s *Service) MileageSummary(ctx context.Context, query url.Values) (MileageSummary, error) {
resolvedQuery, err := s.resolveVehicleQuery(ctx, query)
if err != nil {