From c324de9c32ed7f67cbd88c2c167674ab9f612bf0 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 20 Oct 2026 19:11:31 +0800 Subject: [PATCH] Fix fleet operation hash and history collation comparisons --- .../mapper/OwnedBusinessMapper.java | 12 +++++-- .../mapper/OwnedBusinessQueryMapper.java | 4 ++- .../OwnedBusinessScopePersistenceTest.java | 33 +++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/mapper/OwnedBusinessMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/mapper/OwnedBusinessMapper.java index 4047fc84e..f3831c4a1 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/mapper/OwnedBusinessMapper.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/mapper/OwnedBusinessMapper.java @@ -68,14 +68,20 @@ public interface OwnedBusinessMapper { #{reason},#{snapshotJson},#{occurredAt}) """) int insertHistory(History row); - /** 历史按本页版本顺序分页。 */ + /** 两表文本转连接字符规则后合并,长度取原值以保留中文及真实尾空格;不改原历史或JSON。 */ @Select(""" SELECT * FROM ( SELECT id,dispatch_id,record_version,NULL AS dispatch_record_version,'OPERATIONS' AS event_source, - action,operator_id,reason,snapshot_json,occurred_at FROM ll_fleet_owned_business_history WHERE dispatch_id=#{id} + RPAD(CAST(action AS CHAR(40)),CHAR_LENGTH(action),' ') AS action, + RPAD(CAST(operator_id AS CHAR(128)),CHAR_LENGTH(operator_id),' ') AS operator_id, + RPAD(CAST(reason AS CHAR(2000)),CHAR_LENGTH(reason),' ') AS reason, + snapshot_json,occurred_at FROM ll_fleet_owned_business_history WHERE dispatch_id=#{id} UNION ALL SELECT id,dispatch_id,NULL AS record_version,record_version AS dispatch_record_version,'DISPATCH' AS event_source, - action,operator_id,reason,snapshot_json,occurred_at FROM ll_dispatch_history WHERE dispatch_id=#{id} + RPAD(CAST(action AS CHAR(40)),CHAR_LENGTH(action),' ') AS action, + RPAD(CAST(operator_id AS CHAR(128)),CHAR_LENGTH(operator_id),' ') AS operator_id, + RPAD(CAST(reason AS CHAR(2000)),CHAR_LENGTH(reason),' ') AS reason, + snapshot_json,occurred_at FROM ll_dispatch_history WHERE dispatch_id=#{id} ) events ORDER BY occurred_at DESC,event_source,id DESC LIMIT #{limit} OFFSET #{offset} """) List history(@Param("id") long id,@Param("offset") int offset,@Param("limit") int limit); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/mapper/OwnedBusinessQueryMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/mapper/OwnedBusinessQueryMapper.java index c72c3125c..a3582c459 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/mapper/OwnedBusinessQueryMapper.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/mapper/OwnedBusinessQueryMapper.java @@ -16,10 +16,12 @@ public interface OwnedBusinessQueryMapper { o.loading_completion_hash AS owned_loading_completion_hash,o.loading_completed_at AS owned_loading_completed_at FROM ll_dispatch_order d LEFT JOIN ll_fleet_owned_business_ownership o ON o.dispatch_id=d.id """; + /** 摘要按列上限128字节完整比较,不受新旧表默认排序规则影响,不能省略二进制宽度。 */ String FILTER=""" d.status='ACTIVE' AND d.stage IN ('AFTER_LOADING','DELIVERING','DELIVERED') AND d.loading_completed_at IS NOT NULL AND d.loading_confirmation_json IS NOT NULL - AND o.vehicle_ownership=#{q.scope.ownership} AND o.loading_completion_hash=d.loading_completion_hash + AND o.vehicle_ownership=#{q.scope.ownership} + AND CAST(o.loading_completion_hash AS BINARY(128))=CAST(d.loading_completion_hash AS BINARY(128)) AND o.loading_completed_at=d.loading_completed_at AND (o.resource_mode='MASTER' OR (#{q.scope.ownership}='DISPATCHED' AND o.resource_mode='EXTERNAL')) AND d.planned_departure_at >= #{q.from} diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/fleet/ownedbusiness/OwnedBusinessScopePersistenceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/fleet/ownedbusiness/OwnedBusinessScopePersistenceTest.java index b28002413..b2e75e9ec 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/fleet/ownedbusiness/OwnedBusinessScopePersistenceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/fleet/ownedbusiness/OwnedBusinessScopePersistenceTest.java @@ -229,4 +229,37 @@ class OwnedBusinessScopePersistenceTest { var bound=outsourced.bind(actor(),"DS-OUT-2",slot,body);assertEquals(uploadedPhoto.fileId(),bound.photos().get(0).fileId()); verify(files,times(1)).upload(anyLong(),any(),anyString(),any()); } + /** 完整摘要严格匹配,同前缀末位、大小写和尾空格均不能误命中。 */ + @Test void queryHashComparesCompleteValueRatherThanDefaultBinaryWidth() { + String prefix="a".repeat(63),exact=prefix+"1"; + baseline.jdbc.update("UPDATE ll_dispatch_order SET loading_completion_hash=? WHERE id=1",exact); + baseline.jdbc.update("UPDATE ll_fleet_owned_business_ownership SET loading_completion_hash=? WHERE dispatch_id=1",exact); + var query=new OwnedBusinessModels.Query(1,20,null,null,null,null,null); + assertEquals(1,queries.list(OwnedBusinessFixtures.actor(),query).total()); + for(String different:List.of(prefix+"2","A"+exact.substring(1),prefix+" ")) { + baseline.jdbc.update("UPDATE ll_fleet_owned_business_ownership SET loading_completion_hash=? WHERE dispatch_id=1",different); + var result=queries.list(OwnedBusinessFixtures.actor(),query);assertEquals(0,result.total());assertTrue(result.records().isEmpty()); + } + baseline.jdbc.update("UPDATE ll_fleet_owned_business_ownership SET loading_completion_hash=? WHERE dispatch_id=1",exact); + assertEquals(1,queries.list(OwnedBusinessFixtures.actor(),query).total()); + } + /** 历史投影统一字符比较规则,完整保留中文、原尾空格、动作和原操作账号。 */ + @Test void historyCharacterProjectionPreservesOriginalTextAndSnapshot() { + // 原测试装配的原生历史采用较短替身列,这里只在隔离H2对齐实际原表上限。 + baseline.jdbc.execute("ALTER TABLE ll_dispatch_history ALTER COLUMN operator_id VARCHAR(128)"); + baseline.jdbc.execute("ALTER TABLE ll_dispatch_history ALTER COLUMN reason VARCHAR(2000)"); + baseline.jdbc.execute("ALTER TABLE ll_dispatch_history ALTER COLUMN action VARCHAR(40)"); + String nativeReason="原排车完整中文历史🌏"+"中".repeat(1200)+" ",operationReason="整车承运登记完整中文说明🌏 ",operator="o".repeat(127)+"X",nativeAction="A".repeat(39)+"X"; + baseline.jdbc.update("UPDATE ll_dispatch_history SET reason=?,operator_id=?,action=? WHERE dispatch_id=2",nativeReason,operator,nativeAction); + var saved=outsourced.save(actor(),"DS-OUT-2",new OutsourcedBusinessModels.SaveOperations("0","history-character-original", + new OutsourcedBusinessModels.Operations(new OwnedBusinessModels.Money("CNY","30"),List.of()),operationReason)); + var history=outsourced.history(actor(),"DS-OUT-2",1,20);assertEquals(2,history.total()); + var original=history.records().stream().filter(h->h.eventSource().equals("DISPATCH")).findFirst().orElseThrow(); + var operations=history.records().stream().filter(h->h.eventSource().equals("OPERATIONS")).findFirst().orElseThrow(); + assertEquals(nativeReason,original.reason());assertEquals(nativeAction,original.action()); + assertEquals(operationReason,operations.reason());assertEquals("SAVE_OPERATIONS",operations.action()); + assertEquals(saved.row().operations(),operations.operations());assertEquals("TEST外配操作员",operations.operatorName()); + var raw=baseline.repository.history(2,0,20).stream().filter(h->h.getEventSource().equals("DISPATCH")).findFirst().orElseThrow(); + assertEquals(operator,raw.getOperatorId()); + } }