fix(openflare): serialize access log snowflake IDs as strings

- Return AccessLogView.id as string to avoid JS Number precision loss
- Store OpenFlareAccessLog IDs as uint64 with json id,string
- Update frontend AccessLogItem.id type to string
This commit is contained in:
ryan
2026-06-19 17:40:30 +08:00
parent 3187934f72
commit b6c4181c70
12 changed files with 4556 additions and 678 deletions
+6 -8
View File
@@ -31,21 +31,19 @@ services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-openflare}
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
TZ: ${TZ:-Asia/Shanghai}
ports:
- "${POSTGRES_PORT:-5432}:5432"
- "5432:5432"
environment:
POSTGRES_DB: openflare
POSTGRES_USER: openflare
POSTGRES_PASSWORD: replace-with-strong-password
volumes:
- ./data/postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-openflare}"]
test: ["CMD-SHELL", "pg_isready -U openflare -d openflare"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
redis:
image: valkey/valkey:8.0-alpine
+1
View File
@@ -35,6 +35,7 @@ sidebar: false
- 修复总览看板「24 小时请求趋势」摘要误展示 24 小时累计值的问题:趋势图摘要改为「当前小时」桶数据,顶部 24h 统计改为按小时趋势聚合。
- 修复访问日志 ClickHouse 聚合查询因 `trim(x) AS x` 别名与表列同名导致总览看板地域分布及 IP 统计失败的问题。
- 修复访问日志页 `count()` 扫描类型不匹配(ClickHouse `UInt64` 写入 `int64`)导致列表计数失败的问题。
- 修复访问日志 Snowflake ID 超出 JS 安全整数范围导致列表 React key 重复告警的问题:API `id` 改为字符串序列化。
### 变更
+1715 -239
View File
File diff suppressed because it is too large Load Diff
+1715 -239
View File
File diff suppressed because it is too large Load Diff
+1101 -179
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -523,7 +523,7 @@ export interface AccessLogFilters {
}
export interface AccessLogItem {
id: number;
id: string;
node_id: string;
node_name: string;
logged_at: string;
@@ -4,6 +4,7 @@ package observability
import (
"context"
"errors"
"strconv"
"strings"
"time"
@@ -41,7 +42,7 @@ type AccessLogQuery struct {
// AccessLogView is a single access log row.
type AccessLogView struct {
ID uint `json:"id"`
ID string `json:"id"`
NodeID string `json:"node_id"`
NodeName string `json:"node_name"`
LoggedAt time.Time `json:"logged_at"`
@@ -209,7 +210,7 @@ func ListAccessLogs(ctx context.Context, input AccessLogQuery) (*AccessLogList,
continue
}
views = append(views, AccessLogView{
ID: item.ID,
ID: formatAccessLogID(item.ID),
NodeID: item.NodeID,
NodeName: nodeNames[item.NodeID],
LoggedAt: item.LoggedAt,
@@ -634,3 +635,7 @@ func normalizeFoldMinutes(value int) (int, error) {
return 0, errors.New("fold_minutes 仅支持 3 或 5")
}
}
func formatAccessLogID(id uint64) string {
return strconv.FormatUint(id, 10)
}
+1 -1
View File
@@ -476,7 +476,7 @@ func openFlareAccessLogStatusCodeToInt32(code int) int32 {
}
}
func openFlareAccessLogUintToInt64(value uint) int64 {
func openFlareAccessLogUintToInt64(value uint64) int64 {
if value > math.MaxInt64 {
return math.MaxInt64
}
+2 -2
View File
@@ -216,7 +216,7 @@ func toNodeAccessLogFilter(query OpenFlareAccessLogQuery) analyticsrepo.NodeAcce
func toAnalyticsNodeAccessLog(record *OpenFlareAccessLog) analyticsmodel.NodeAccessLog {
return analyticsmodel.NodeAccessLog{
ID: uint64(record.ID),
ID: record.ID,
NodeID: record.NodeID,
LoggedAt: record.LoggedAt,
RemoteAddr: record.RemoteAddr,
@@ -232,7 +232,7 @@ func fromAnalyticsNodeAccessLogs(rows []analyticsmodel.NodeAccessLog) []*OpenFla
result := make([]*OpenFlareAccessLog, len(rows))
for index, row := range rows {
result[index] = &OpenFlareAccessLog{
ID: uint(row.ID),
ID: row.ID,
NodeID: row.NodeID,
LoggedAt: row.LoggedAt,
RemoteAddr: row.RemoteAddr,
@@ -28,7 +28,7 @@ func (s *memoryAccessLogStore) InsertBatch(_ context.Context, records []*OpenFla
}
copyRecord := *record
if copyRecord.ID == 0 {
copyRecord.ID = uint(idgen.NextUint64ID())
copyRecord.ID = idgen.NextUint64ID()
}
if copyRecord.CreatedAt.IsZero() {
copyRecord.CreatedAt = now
+1 -1
View File
@@ -60,7 +60,7 @@ func (OpenFlareRequestReport) TableName() string {
// OpenFlareAccessLog stores a single access log row in ClickHouse (database: openflare, table: of_node_access_logs).
// ClickHouse DDL is managed by goose; reads/writes go through internal/repository/analytics.
type OpenFlareAccessLog struct {
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
ID uint64 `json:"id,string" gorm:"column:id"`
NodeID string `json:"node_id" gorm:"index;size:64;not null"`
LoggedAt time.Time `json:"logged_at" gorm:"index"`
RemoteAddr string `json:"remote_addr" gorm:"index;size:128"`
@@ -356,7 +356,7 @@ func sortOpenFlareMetricSnapshots(items []*OpenFlareMetricSnapshot) {
if compare := openFlareAccessLogCompareInt64(left.CapturedAt.Unix(), right.CapturedAt.Unix()); compare != 0 {
return compare > 0
}
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(left.ID), openFlareAccessLogUintToInt64(right.ID)) > 0
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(uint64(left.ID)), openFlareAccessLogUintToInt64(uint64(right.ID))) > 0
})
}
@@ -370,7 +370,7 @@ func sortOpenFlareRequestReports(items []*OpenFlareRequestReport) {
if compare := openFlareAccessLogCompareInt64(left.WindowEndedAt.Unix(), right.WindowEndedAt.Unix()); compare != 0 {
return compare > 0
}
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(left.ID), openFlareAccessLogUintToInt64(right.ID)) > 0
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(uint64(left.ID)), openFlareAccessLogUintToInt64(uint64(right.ID))) > 0
})
}
@@ -384,7 +384,7 @@ func sortOpenFlareNodeObservationOpenresty(items []*OpenFlareNodeObservationOpen
if compare := openFlareAccessLogCompareInt64(left.CapturedAt.Unix(), right.CapturedAt.Unix()); compare != 0 {
return compare > 0
}
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(left.ID), openFlareAccessLogUintToInt64(right.ID)) > 0
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(uint64(left.ID)), openFlareAccessLogUintToInt64(uint64(right.ID))) > 0
})
}
@@ -398,7 +398,7 @@ func sortOpenFlareNodeObservationFrps(items []*OpenFlareNodeObservationFrps) {
if compare := openFlareAccessLogCompareInt64(left.CapturedAt.Unix(), right.CapturedAt.Unix()); compare != 0 {
return compare > 0
}
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(left.ID), openFlareAccessLogUintToInt64(right.ID)) > 0
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(uint64(left.ID)), openFlareAccessLogUintToInt64(uint64(right.ID))) > 0
})
}
@@ -412,7 +412,7 @@ func sortOpenFlareNodeObservationFrpc(items []*OpenFlareNodeObservationFrpc) {
if compare := openFlareAccessLogCompareInt64(left.CapturedAt.Unix(), right.CapturedAt.Unix()); compare != 0 {
return compare > 0
}
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(left.ID), openFlareAccessLogUintToInt64(right.ID)) > 0
return openFlareAccessLogCompareInt64(openFlareAccessLogUintToInt64(uint64(left.ID)), openFlareAccessLogUintToInt64(uint64(right.ID))) > 0
})
}