33using System . IO ;
44using System . IO . Compression ;
55using System . Linq ;
6+ using System . Security . Cryptography ;
67using System . Threading . Tasks ;
78using Magicodes . ExporterAndImporter . Core ;
89using Magicodes . ExporterAndImporter . Excel ;
@@ -139,9 +140,8 @@ public class OnePicRowDto
139140 }
140141
141142 /// <summary>
142- /// 验证 SHA1 改动后图片字节内容完全一致:
143- /// 1 张图 vs 100 张同图的 xlsx (两个文件都走 IExporter.Export 生成),
144- /// 提取 /xl/media/image1.* 的 zip-level CRC32, 应完全一致.
143+ /// 验证 SHA1 改动后图片字节内容一致:
144+ /// 1 张图 vs 100 张同图的 xlsx, /xl/media/image1.* 原始字节 SHA1 应完全一致.
145145 /// </summary>
146146 public class ImageByteConsistency_Tests : TestBase
147147 {
@@ -158,11 +158,7 @@ private static string GetSamplePngPath() =>
158158 [ Fact ( DisplayName = "ImageByteConsistency_SingleVsRepeatedDrawings_ImageBytesIdentical" ) ]
159159 public async Task SingleVsRepeatedDrawings_ImageBytesIdentical ( )
160160 {
161- // baseline: 1 行 1 列 = 1 张图
162- // repeat: 100 行 1 列 = 100 张同图 (走 IE 同图去重路径)
163- // 两个 xlsx 都走 IExporter.Export 生成 (与生产路径一致),
164- // 用 ZipArchive 提取 /xl/media/image1.*, 对比 zip-level CRC32,
165- // 应完全一致 → 确认 SHA1 改动未改变图片字节内容.
161+ // baseline: 1 张图, repeat: 100 张同图 (走 IE 同图去重路径)
166162 var singlePath = GetTestFilePath ( $ "{ nameof ( SingleVsRepeatedDrawings_ImageBytesIdentical ) } _single.xlsx") ;
167163 var repeatPath = GetTestFilePath ( $ "{ nameof ( SingleVsRepeatedDrawings_ImageBytesIdentical ) } _repeat.xlsx") ;
168164 DeleteFile ( singlePath ) ;
@@ -176,78 +172,26 @@ public async Task SingleVsRepeatedDrawings_ImageBytesIdentical()
176172 var repeatData = DrawingsFlush_Tests . BuildOnePicRowDto ( 100 , GetSamplePngPath ( ) ) ;
177173 await exporter . Export ( repeatPath , repeatData ) ;
178174
179- var singleCrc = ExtractFirstImageCrc32 ( singlePath ) ;
180- var repeatCrc = ExtractFirstImageCrc32 ( repeatPath ) ;
175+ var singleHash = ComputeFirstImageHash ( singlePath ) ;
176+ var repeatHash = ComputeFirstImageHash ( repeatPath ) ;
181177
182- singleCrc . ShouldBe ( repeatCrc ) ;
183- _output . WriteLine ( $ "single (1 行) image1 CRC32 = 0x { singleCrc : X8 } , repeat (100 行) image1 CRC32 = 0x { repeatCrc : X8 } ") ;
178+ singleHash . ShouldBe ( repeatHash ) ;
179+ _output . WriteLine ( $ "single (1 行) image1 SHA1 = { singleHash } , repeat (100 行) image1 SHA1 = { repeatHash } ") ;
184180 }
185181
186182 /// <summary>
187- /// 从 xlsx zip 中提取第一个 /xl/media/image* 条目的 CRC32 (zip-level).
188- /// net471 上 ZipArchiveEntry.Crc32 不存在, 走流手算; 其他 target 直接用原生属性.
183+ /// 从 xlsx zip 读取第一个 /xl/media/image* 条目的全部字节, 返回 SHA1 十六进制.
189184 /// </summary>
190- private static uint ExtractFirstImageCrc32 ( string xlsxPath )
185+ private static string ComputeFirstImageHash ( string xlsxPath )
191186 {
192187 using var archive = ZipFile . OpenRead ( xlsxPath ) ;
193188 var imageEntry = archive . Entries
194189 . Where ( e => e . FullName . StartsWith ( "xl/media/" , StringComparison . OrdinalIgnoreCase ) )
195190 . OrderBy ( e => e . FullName , StringComparer . Ordinal )
196191 . First ( ) ;
197- #if NET471
198- using ( var stream = imageEntry . Open ( ) )
199- {
200- return ComputeZipEntryCrc32 ( stream ) ;
201- }
202- #else
203- return imageEntry . Crc32 ;
204- #endif
205- }
206-
207- #if NET471
208- /// <summary>
209- /// IEEE 802.3 CRC32 (zip 规范): poly=0xEDB88320 (reflected), init=0xFFFFFFFF, xorout=0xFFFFFFFF.
210- /// 与 ZipArchiveEntry.Crc32 (NET6+) 字节级一致 — 已用 System.IO.Hashing.Crc32 在 11 个样本
211- /// (含 png 实测数据) + 真实 zip entry 四路对比验证. 表在类型加载时按 poly 运行时生成,
212- /// 避免硬编码 256 项引入抄写错误 (CLR 保证 static readonly 初始化线程安全).
213- /// 循环风格对齐 dotnet/runtime 的 Crc32ParameterSet.UpdateScalar.
214- /// </summary>
215- private static readonly uint [ ] ZipCrc32LookupTable = BuildZipCrc32LookupTable ( ) ;
216-
217- private static uint [ ] BuildZipCrc32LookupTable ( )
218- {
219- const uint polynomial = 0xEDB88320u ;
220- var lookupTable = new uint [ 256 ] ;
221- for ( uint i = 0 ; i < 256 ; i ++ )
222- {
223- uint c = i ;
224- for ( int k = 0 ; k < 8 ; k ++ )
225- {
226- c = ( c & 1u ) != 0 ? ( c >> 1 ) ^ polynomial : c >> 1 ;
227- }
228- lookupTable [ i ] = c ;
229- }
230- return lookupTable ;
231- }
232-
233- private static uint ComputeZipEntryCrc32 ( Stream stream )
234- {
235- uint [ ] lookupTable = ZipCrc32LookupTable ;
236- System . Diagnostics . Debug . Assert ( lookupTable . Length == 256 ) ;
237- uint crc = 0xFFFFFFFFu ;
238- // net471 没有 Stream.Read(Span<byte>), 用 byte[] + int 偏移.
239- var buffer = new byte [ 4096 ] ;
240- int read ;
241- while ( ( read = stream . Read ( buffer , 0 , buffer . Length ) ) > 0 )
242- {
243- for ( int i = 0 ; i < read ; i ++ )
244- {
245- byte idx = ( byte ) ( crc ^ buffer [ i ] ) ;
246- crc = lookupTable [ idx ] ^ ( crc >> 8 ) ;
247- }
248- }
249- return crc ^ 0xFFFFFFFFu ;
192+ using var stream = imageEntry . Open ( ) ;
193+ using var sha1 = SHA1 . Create ( ) ;
194+ return BitConverter . ToString ( sha1 . ComputeHash ( stream ) ) . Replace ( "-" , string . Empty ) ;
250195 }
251- #endif
252196 }
253197}
0 commit comments