Compare commits

...

2 Commits

Author SHA1 Message Date
lingniu
91db6c959e docs: record jt808 identity tracking verification 2026-07-01 23:23:59 +08:00
lingniu
7b44f97ddb fix: decode jt808 registration plate text 2026-07-01 23:21:53 +08:00
4 changed files with 89 additions and 1 deletions

View File

@@ -431,3 +431,57 @@ LKLG7C4E3NA774736 | JT808 | 2026-07-01 | daily_total_mileage_km | 12345.600 | sa
``` ```
说明:生产 TDengine 中已经存在大量 808 非零 `total_mileage_km` 采样,但许多 phone 尚未映射到 VIN因此 MySQL 按 VIN 的统计只会覆盖已解析 VIN 的车辆。下一步应继续补齐 `vehicle_identity_binding`,让更多 808 车辆进入统计。 说明:生产 TDengine 中已经存在大量 808 非零 `total_mileage_km` 采样,但许多 phone 尚未映射到 VIN因此 MySQL 按 VIN 的统计只会覆盖已解析 VIN 的车辆。下一步应继续补齐 `vehicle_identity_binding`,让更多 808 车辆进入统计。
## 2026-07-01 23:23 808 注册身份自动维护复验
部署版本已更新:
- Git commit`7b44f97`
- 镜像:`crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com/oneos/vehicle-gateway-go:go-7b44f97-20260701232200`
本轮修复内容:
- 808 `0x0100` 注册帧解析 `province``city``manufacturer``device_type``device_id``plate_color``plate`
- 808 `0x0102` 鉴权帧解析 `auth_token`
- Go gateway 在 identity resolve 后写入 `jt808_registration`,普通上行也会刷新 `latest_seen_at`
- 注册帧通过车牌解析到 VIN 后,会尝试把 phone/device/plate 反写到 `vehicle_identity_binding`
- 注册帧车牌支持 GBK 解码,避免 `Incorrect string value` 写入错误。
验证命令:
```text
go test ./...
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/gateway
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/history-writer
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/stat-writer
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/realtime-api
```
ECS 运行状态:
```text
go-vehicle-gateway | go-7b44f97-20260701232200 | 0.0.0.0:808->808, 0.0.0.0:32960->32960
go-history-writer | go-7b44f97-20260701232200 | Up
go-stat-writer | go-7b44f97-20260701232200 | Up
go-realtime-api | go-7b44f97-20260701232200 | 0.0.0.0:20210->20210
```
生产日志确认:
- 新版本启动后真实 808、32960 连接持续进入。
- GBK 车牌注册帧不再出现 `Incorrect string value`
MySQL `jt808_registration` 确认:
```text
registration_rows=214
distinct_phone=214
vin_rows=74
plate_rows=74
device_rows=72
013079963379 | device_id=9963379 | plate=沪A06788F | vin=LKLG7C4E3NA774736 | latest_registered_at=2026-07-01 23:23:01
013079963321 | device_id=9963321 | plate=沪A59613F | vin=LKLG7C4EXNA774796 | latest_registered_at=2026-07-01 23:23:04
```
说明:`jt808_registration.phone` 保存 808 包头原始 phone`vehicle_identity_binding.phone` 中已有部分去前导零 phone。Go resolver 会同时查询原始 phone 和去前导零 phone。

View File

@@ -10,6 +10,7 @@ require (
github.com/redis/go-redis/v9 v9.17.2 github.com/redis/go-redis/v9 v9.17.2
github.com/segmentio/kafka-go v0.4.49 github.com/segmentio/kafka-go v0.4.49
github.com/taosdata/driver-go/v3 v3.8.1 github.com/taosdata/driver-go/v3 v3.8.1
golang.org/x/text v0.29.0
) )
require ( require (

View File

@@ -1,14 +1,17 @@
package jt808 package jt808
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"time" "time"
"unicode/utf8"
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope" "lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
"golang.org/x/text/encoding/simplifiedchinese"
) )
var ( var (
@@ -318,7 +321,18 @@ func fixedText(data []byte) string {
} }
func freeText(data []byte) string { func freeText(data []byte) string {
return strings.TrimRight(strings.TrimSpace(string(data)), "\x00") data = bytes.Trim(data, "\x00 ")
if len(data) == 0 {
return ""
}
if utf8.Valid(data) {
return string(data)
}
decoded, err := simplifiedchinese.GBK.NewDecoder().Bytes(data)
if err == nil && utf8.Valid(decoded) {
return string(decoded)
}
return strings.ToValidUTF8(string(data), "")
} }
func parseBCDTime(data []byte) time.Time { func parseBCDTime(data []byte) time.Time {

View File

@@ -153,6 +153,25 @@ func TestParseFrameParsesRegistrationIdentity(t *testing.T) {
} }
} }
func TestParseFrameDecodesGBKRegistrationPlate(t *testing.T) {
body := make([]byte, 0, 46)
body = binary.BigEndian.AppendUint16(body, 16)
body = binary.BigEndian.AppendUint16(body, 32)
body = appendFixedASCII(body, "YUTNG", 5)
body = appendFixedASCII(body, "ZK6105CHEVNPG4", 20)
body = appendFixedASCII(body, "DEV0002", 7)
body = append(body, 2)
body = append(body, []byte{0xBB, 0xA6, 'A', '5', '3', '3', '0', '1'}...)
env, err := ParseFrame(buildFrame(0x0100, "013079963380", 9, body), 1782918600000, "115.231.168.135:43625")
if err != nil {
t.Fatalf("ParseFrame() error = %v", err)
}
if env.Plate != "沪A53301" {
t.Fatalf("plate = %q", env.Plate)
}
}
func TestParseFrameParsesAuthenticationToken(t *testing.T) { func TestParseFrameParsesAuthenticationToken(t *testing.T) {
env, err := ParseFrame(buildFrame(0x0102, "064646848757", 8, []byte("g7gps")), 1782918600000, "115.231.168.135:43625") env, err := ParseFrame(buildFrame(0x0102, "064646848757", 8, []byte("g7gps")), 1782918600000, "115.231.168.135:43625")
if err != nil { if err != nil {