{"record":{"id":"3a1c5e8a0808647e","repo":"JeffreySu/WeiXinMPSDK","slug":"streamfactory","errorCode":null,"errorMessage":"streamFactory 返回的流必须可写。","messagePattern":"streamFactory 返回的流必须可写。","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/BasePay/BasePayApis.cs","lineNumber":921,"sourceCode":"                throw new ArgumentNullException(nameof(streamFactory));\n            }\n\n            try\n            {\n                var url = BasePayApis.GetPayApiUrl($\"{Senparc.Weixin.Config.TenPayV3Host}/{{0}}v3/bill/sub-merchant-fundflowbill{UrlQueryHelper.ToParams(data)}\");\n                TenPayApiRequest tenPayApiRequest = new(_tenpayV3Setting);\n                var result = await tenPayApiRequest.RequestAsync<SubmerchantBillReturnJson>(url, null, cancellationToken, timeOut, ApiRequestMethod.GET).ConfigureAwait(false);\n\n                //下载资金账单\n                if (result.VerifySignSuccess == true)\n                {\n                    foreach (var item in result.download_bill_list ?? new List<SubmerchantBillDownloadItem>())\n                    {\n                        cancellationToken.ThrowIfCancellationRequested();\n                        var fileStream = streamFactory(item) ?? throw new InvalidOperationException(\"streamFactory 不能返回 null。\");\n                        if (!fileStream.CanWrite)\n                        {\n                            throw new ArgumentException(\"streamFactory 返回的流必须可写。\", nameof(streamFactory));\n                        }\n                        var fileVerify = await TenPayDownloadHelper.DownloadAndVerifyAsync(\n                            tenPayApiRequest, item.download_url, fileStream, item.hash_type, item.hash_value,\n                            timeOut, cancellationToken).ConfigureAwait(false);\n                        if (!fileVerify)\n                        {\n                            result.VerifySignSuccess = false;\n                            result.ResultCode.Additional += \"请求成功，但文件校验错误。请查看日志！\";\n                            SenparcTrace.BaseExceptionLog(new TenpayApiRequestException($\"SubmerchantFundflowBillQueryAsync 下载文件成功，但流式校验失败，正确值：{item.hash_value}\"));\n                        }\n                    }\n                }\n\n                return result;\n            }\n            catch (OperationCanceledException)\n            {\n                throw;","sourceCodeStart":903,"sourceCodeEnd":939,"githubUrl":"https://github.com/JeffreySu/WeiXinMPSDK/blob/be573f6f94bdbf718dd5f6cdecb137fbc7ff651e/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/BasePay/BasePayApis.cs#L903-L939","documentation":"After obtaining the stream from streamFactory, the library checks fileStream.CanWrite; streams opened read-only cannot receive the downloaded bill, so an ArgumentException naming the streamFactory parameter is thrown.","triggerScenarios":"Passing a streamFactory that returns a read-only stream — e.g. File.OpenRead, a FileStream opened with FileAccess.Read, or a MemoryStream backed by a read-only wrapped stream.","commonSituations":"Copying file-open code from a download/reading code path into the factory; opening an existing file without write access; using a stream wrapper that blocks writes.","solutions":["Open the stream with write access (File.Create, File.Open(path, FileMode.Create, FileAccess.Write)).","If appending is intended, use File.Open(path, FileMode.Append, FileAccess.Write).","For memory output, use new MemoryStream() which is writable by default."],"exampleFix":"// before\n() => File.OpenRead(path)\n// after\n() => File.Open(path, FileMode.Create, FileAccess.Write)","handlingStrategy":"validation","validationCode":"using var s = streamFactory(item);\nif (!s.CanWrite) throw new ArgumentException(\"streamFactory stream must be writable.\");","typeGuard":"bool isWritableStream(Stream? s) => s is { CanWrite: true };","tryCatchPattern":"try { await apis.DownloadFundFlowBillAsync(..., streamFactory); }\ncatch (ArgumentException ex) when (ex.ParamName == \"streamFactory\") { logger.Error(ex, \"Read-only stream supplied\"); throw; }","preventionTips":["Open download targets with FileAccess.Write / FileMode.Create","Do not reuse File.OpenRead streams as download targets","Unit-test the factory asserting CanWrite"],"tags":["stream","file-io","argument-validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"be573f6f94bdbf718dd5f6cdecb137fbc7ff651e","analyzedAt":"2026-09-12T10:01:50.733Z","contentChangedAt":"2026-09-12T10:01:50.733Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}