资讯动态

C#实现医保接口自动传输:Windows服务开发与部署全指南

发布时间:2026/10/8 16:49:54 来源:尧图企业网站定制
简介面向医疗信息化开发者的C# Windows服务版医保接口自动传输程序用于解决医疗机构与医保系统之间患者信息、诊疗项目、药品费用等数据的定时、自动、可靠交换。程序采用System.ServiceProcess中的ServiceBase构建服务主类配合医保通信接口、数据模型、配置管理与日志模块适合需要掌握Windows服务开发与医保接口对接的中高级.NET工程师参考。压缩包共41个文件大小仅61KB以cs源代码、config配置、txt说明、bat脚本、exe可执行程序等为主同时包含sln工程文件、resx资源与pdb调试符号结构清晰便于二次开发与排错。源码中覆盖数据库访问、错误日志记录、服务安装等关键实现可借鉴服务生命周期管理、接口封装和异常恢复机制。目前已有217人学习下载对希望快速落地医保数据自动传输场景的开发者而言是一份轻量完整的入门范本。1. 医保接口自动传输程序到底是什么先看懂那个总被催的“传不上来”医院信息科每天最怕听到的就是“医保传不上去了”。所谓医保接口自动传输程序就是替人干这件活的常驻后台程序HIS把费用明细、就诊记录按约定格式导出成文件程序定时拉走、推到医保前置机或调用医保平台的接口再把返回结果带回来全程不需要人盯着。看到“winservic”这个拼写其实就是Windows Service的简写或笔误——一个以Windows服务形态运行的C#程序。它适合两类人给医院做HIS集成的实施工程师以及要维护医保接口的医院信息科开发。接下来按一套可以直接照搬的方案讲接口形态怎么判断、服务怎么搭、传输怎么重试、坑在哪。2. 医保接口自动传输的接口形态与选型文件型、WebService型和运行载体先要把“接口”这个黑匣子拆开。医保接口自动传输程序的上游是HIS导出的文件或待上传表下游是医保中心放在医院侧的前置机、或直接指向医保中心的WebService地址。这两端的数据怎么见面决定了程序怎么写。2.1 文件型接口HIS导出、前置机投递、结果回读文件型接口在医保接入里占了相当大比例尤其是地市级的老系统。它的典型链路分三步HIS在约定时间把参保人就诊信息、费用明细、退费记录等按医保接口文档的字段顺序导出成固定格式的TXT或XML传输程序负责把文件从HIS的导出目录搬到医保前置机的接收目录医保前置机上的进程读走文件后会生成一个结果文件有的叫反馈文件、有的叫对账文件放在另一个目录传输程序再把它搬回来交给HIS处理。这个模型里程序有三件事要做探测新文件、校验文件是否完整、搬运并做台账。探测新文件常见做法有FileSystemWatcher和定时扫描两种。FileSystemWatcher响应快但服务一重启、文件在网络盘上时就容易丢事件我一般宁可选择定时扫描牺牲一两秒的实时性换可靠性。校验完整是容易被忽略的步骤HIS还在写文件时程序就动手搬传过去的是半截内容医保端解析必然报错。这里先记住原则——没有“写完”标记的文件不要碰。文件型接口的好处是逻辑简单坏处是协议描述文件里全是字段长度和偏移量一旦字段错位返回的报错信息又只有“记录超长”这类笼统话排错非常难受。如果你手头的接入文档是几百行字段定义表那基本就是这种形态。2.2 WebService型接口上送、下载、冲正另一种主流形态是WebService接口。HIS不落文件程序从数据库的待上传表里按业务主键取数组装成XML报文POST到医保系统给的URL同步拿返回结果。这类接口的动作一般分三种上送结算单据、退费单据、下载医保目录、人员信息、冲正把传错的单子作废重传。WebService型接口的麻烦不在传输在报文构造和签名。很多医保中心会要求对报文体做MD5或3DES加密密钥在接入调试时发放有的要用时间戳参与签名防止重复请求。第4章我会给一个带签名的调用骨架你照着填参数就行。还有一种相对少见的“中间库”型接口医保前置机上建一组表HIS往里插数据医保进程按状态字段取数、回写结果。这种本质上是文件型的一个变种传输程序退化为一个定时SQL搬运工处理起来反而更简单。2.3 运行载体选型为什么是Windows服务而不是计划任务接口形态定了接下来选程序怎么跑。就“自动传输”这四个字运行载体有四个候选控制台程序、计划任务、Windows服务、以服务方式寄宿的托管程序比如用Topshelf封装。控制台程序只适合调试没有人会24小时开着登录会话保一个黑窗口一关电脑传输就断。计划任务稍微好一点能开机触发、能间隔运行但它最大的问题是“跑完即走”这次任务中途断网了重试状态只能靠程序自己写在文件里而且任务计划程序本身也是非交互环境出问题后用户感知极差。Windows服务就是标题里的winservic是Windows平台上做长期常驻的“正规军”用户注销照跑、开机自动启动、挂掉后可以配置服务恢复策略自动拉起还有服务管理器统一看着。C#里写Windows服务有现成的ServiceBase基类不涉及额外运行时依赖这比用脚本打包成服务再套一层壳要干净得多。这里有个选择建议如果你所在的医院机房能接受用Topshelf这类库开发体验会好不少但很多医保前置机就是一台老旧Windows Server运行时环境里连别的依赖都没有纯.NET Framework的ServiceBase反而是最稳妥的。下面整篇的代码都按ServiceBase写法给出因为它不依赖任何第三方框架。运行载体对比运行载体开机自启断线重试故障自愈运维难度控制台不支持靠代码无低计划任务需配置靠代码靠系统日志中Windows服务支持靠代码支持服务恢复低Windows服务的故障自愈具体指服务管理器里“失败后重新启动”的策略这个在第5章部署细节里会讲。2.4 拿到接入文档后先做三件事第一次接医保接口时别急着写上传代码。我一般会先做三个动作一是确认文件或报文的字符编码很多医保中心还在用GBK程序里写死了UTF-8必然乱码二是确认时间同步签名或对账都依赖服务器时钟时间偏差超过几分钟就会出现“时间戳不在有效期内”这种报错三是确认网络边界前置机的端口是只对指定IP开放还是全开这个决定了调试时要不要找医保方临时加白名单。这三件事不确认好后面写的每一行代码都是在给排错埋雷。尤其是字符编码C#里Encoding.GetEncoding(GBK)在.NET Framework下没问题但在.NET Core上还需要注册代码页编码提供程序这个坑我在第5章专门列出来。3. 用C#搭一套能落地的WinService骨架服务生命周期与定时轮询这一章是整套方案的承重墙。项目结构、生命周期、轮询方式都在这章定死后面传输出错也只在这一章定的框架里补。3.1 项目结构与最小可编译服务类用Visual Studio新建项目时选C#下的“Windows 服务(.NET Framework)”模板也可以先建普通控制台项目再手动改造但模板能帮你自动生成Program.cs和安装所需的程序集信息省事。解决方案结构我习惯分两层一个Windows服务项目负责生命周期一个核心类库负责传输逻辑配置放在服务项目的App.config里。先看服务主体。继承ServiceBase的类负责两件事启动和停止。public partial class HiMedTransferService : ServiceBase { private CancellationTokenSource _cts; private Thread _worker; public HiMedTransferService() { InitializeComponent(); ServiceName HiMedAutoTransfer; // 服务注册名安装后用这个名字管理 } protected override void OnStart(string[] args) { _cts new CancellationTokenSource(); _worker new Thread(() WorkerLoop(_cts.Token)) { IsBackground true, Name TransferWorker }; _worker.Start(); } protected override void OnStop() { _cts?.Cancel(); // 给当前批次最多3秒收尾避免文件传一半 _worker?.Join(3000); } }这段代码里有两个关键设计。一是用Thread而不是直接开Task做WorkerLoop原因是Task默认跑在线程池上而Windows服务的启动和停止与线程池之间偶尔会有互动问题用显式Thread生命周期更可控。二是用CancellationToken做停止信号而不是用一个bool标志位这样在WorkerLoop里可以针对取消做更精细的处理比如等当前文件传完再停。提示ServiceName必须和安装时注册的服务名保持一致。如果不一致服务能装上但启动时服务管理器找不到对应入口事件查看器会报“服务名无效”。3.2 轮询主循环用WaitOne而不是手动Sleep传输程序不需要像上位机那样高频刷新几秒到几十秒轮询一次足够。写轮询循环最忌讳的是在循环末尾直接Thread.Sleep原因有二Sleep期间服务停止事件无法及时响应服务管理器会判定“服务没有及时响应”而强制杀进程另外Sleep的时间是“上一次跑完后”再睡“运行时长加睡眠时长”会导致实际周期漂移。轮询周期应该从“每次循环开始”这个基准点来算。实现一个固定周期的轮询循环private void WorkerLoop(CancellationToken token) { TimeSpan interval TimeSpan.FromSeconds(Config.PollIntervalSeconds); var nextTick DateTime.UtcNow; while (!token.IsCancellationRequested) { nextTick nextTick.Add(interval); try { PollOnce(token); } catch (Exception ex) { Logger.Error(ex, 轮询主循环异常); } var delayMs (int)(nextTick - DateTime.UtcNow).TotalMilliseconds; if (delayMs 0 token.WaitHandle.WaitOne(delayMs)) { break; // 收到停止信号及时退出 } } }这个循环的精髓在最后几行用token.WaitHandle.WaitOne(delayMs)代替Thread.Sleep。WaitOne在到达时间前如果收到取消信号会立即返回true循环立刻break如果是正常睡眠到点则返回false继续下一次轮询。这样服务停止的响应延迟最多不超过一个轮询间隔不会触发服务管理器的看门狗。PollOnce是核心逻辑的入口里面依次做四件事检查是否有新文件或待传数据、校验数据完整性、执行上传并写台账、处理返回结果。这四步的具体实现看第4章。3.3 App.config参数设计把环境差异挡在编译之外医保接口最大的特点是环境差异大医院正式库、测试库、医保测试前置机目录不同、IP不同、密钥不同。如果把地址写在代码里每换一次环境就得重新编译非常狼狈。所以全部环境差异项都挪到App.config里程序启动时用ConfigurationManager读出来放到一个静态Config类中。配置文件的关键章节appSettings !-- 轮询间隔单位秒正式环境30~60调试时可设5 -- add keyPollIntervalSeconds value30 / !-- HIS导出目录文件型接口用 -- add keySourceDirectory valueD:\HISExport / !-- 医保前置机FTP/SFTP参数 -- add keyDestServer value192.168.10.20 / add keyDestPort value22 / add keyDestUser valueftpuser / add keyDestPassword value加密后的口令 / add keyDestDirectory value/upload/inbound/ / !-- 已发送台账目录程序成功上传后把文件移到这里 -- add keySentDirectory valueD:\HISExport\sent / !-- 文件编码GBK或utf-8 -- add keyFileEncodingName valueGBK / /appSettings参数说明PollIntervalSeconds决定实时性与负载的折中设太短会频繁扫描目录制造无谓IO生产环境一般用30秒DestPassword在配置里最好不要明文写常见做法是存一个加密串程序启动时用本机密钥解密也可以把密钥放到注册表里第5章会提到用注册表存储的替代方案。我在实际项目里还会部署一个开关控制“是否允许重复实例”防止同一配置在服务器上被重复安装两份导致文件被抢传。这个开关放在服务安装参数里而不是App.config。3.4 写一个带状态的“本地台账”比数据库轻量的记录方式文件型传输除了搬文件还要回答一个核心问题“哪个文件传过了”这直接决定了断线重连后会不会重复上传。我不建议用内存HashSet记录文件列表因为服务一重启就丢最终还是要靠磁盘。常见的可靠方案有两种传完的文件移到sent目录重传时目录里找不到就不传或者维护一份CSV台账记录文件名、MD5、时间、状态。移目录的方式最直观但会丢失“这个文件在传输过程中发生过网络错误”的历史台账方式适合要知道失败原因的运维场景。我习惯用C#值元组解构写简版台账判断逻辑private bool IsAlreadySent(string fileName, string md5) { return File.ReadLines(_recordPath) .Select(line line.Split(,)) .Any(cols cols.Length 3 cols[0] fileName cols[1] md5); }这个写法依赖C#的LINQ和元组解构语法从可读性上说比手写foreach循环更紧凑。台账文件本身要控制大小每传一个文件加一行时间久了会变成几千行读取压力并不大但建议每周做一次归档把超过30天的行清理掉否则运维排查问题时台账已经比日志还大了。4. 传输与返回结果处理SFTP上传、WebService调用与重试队列第3章把骨架搭起来了这一章往PollOnce里填肉。传输动作写对了程序就算成了一半另一半是对返回结果和异常的处理这部分直接决定程序是“会自动重试的好程序”还是“看心情传的程序”。4.1 SFTP上传优先选SFTP而不是FTP医保前置机的文件接收方式老的用FTP新的为了安全大多用SFTP。C#做SFTP最成熟的方案是Renci.SshNet库NuGet包名SSH.NET另一个选择是调用WinSCP的命令行但WinSCP要求目标机器装客户端且依赖外部进程纯托管方案里SSH.NET更干净。上传文件的代码骨架public bool UploadViaSftp(string localFile, string remoteDir) { using var client new SftpClient(Config.DestServer, Config.DestPort, Config.DestUser, Decrypt(Config.DestPassword)); client.Connect(); try { // 先切到目标目录避免远端路径拼接出错 client.ChangeDirectory(remoteDir); using var fs File.OpenRead(localFile); var remoteName Path.GetFileName(localFile); client.UploadFile(fs, remoteName); return true; } catch (SftpPathNotFoundException ex) { Logger.Error(ex, 远端目录不存在{0}, remoteDir); throw new RemoteDirMissingException(remoteDir); } finally { if (client.IsConnected) { client.Disconnect(); } } }这段代码有两个细节值得说。一是Connect之后先ChangeDirectory再检查远端目录是否存在很多实现是直接把远端目录拼进路径去UploadFile一旦远端目录不存在抛的异常是路径相关的但无法区分是目录缺失还是权限不足。二是finally里先判断IsConnected再Disconnect防止Connect本身失败时Disconnect二次抛异常这个顺序写反了就是典型的低级翻车。如果HIS导出目录和传输程序不在同一台机器上需要从网络共享路径读文件File.OpenRead传入UNC路径即可但服务的运行账号必须有该共享目录的读权限。这一条在部署文档里要单独列出来否则安装的服务用LocalSystem账号去读另一台机器的共享十有八九报“访问被拒绝”。4.2 调用医保WebService带签名、超时和返回码解析文件型接口之外越来越多的医保接口改成WebService。这一节以HttpClient直接发SOAP报文为例不用老旧的WebReference代理类因为代理类在.NET Framework下生成的代码依赖特定版本升级环境时很容易出问题。public async Taskstring CallMihsoWebService(string actionName, string requestXml) { using var http new HttpClient(); http.Timeout TimeSpan.FromSeconds(30); // 医保接口慢但也不能无限等 // 多数医保接口要求content-type为text/xmlcharset按接入文档来 var content new StringContent(requestXml, Encoding.UTF8, text/xml); // 按接口文档要求追加Header常见是时间戳加MD5签名 var ts DateTime.Now.ToString(yyyyMMddHHmmss); var sign SignHelper.Md5(Config.AppKey ts Config.AppSecret); content.Headers.Add(X-Timestamp, ts); content.Headers.Add(X-Sign, sign); var resp await http.PostAsync(Config.MihsoEndpoint, content); var body await resp.Content.ReadAsStringAsync(); if (!resp.IsSuccessStatusCode) { // 先记日志再抛异常便于定位是网络还是业务错误 Logger.Warn(HTTP {0}接口返回{1}, (int)resp.StatusCode, Truncate(body, 200)); throw new MihsoHttpException((int)resp.StatusCode, body); } return body; }签名这块要强调一个参数很多接口要求的是“时间戳加密钥拼接后MD5”不是“密钥加时间戳”顺序错了签名校验必失败。我吃过这个亏养成了一个习惯先拿接入文档里的例子报文手工算一遍签名确认算法再写代码不要想当然。另外这个调用里没有做重试重试放到第4.3节的重试队列统一管不要在HttpClient外层套多个循环。返回结果的解析同样要小心。医保WebService的返回报文多数是XML少数是JSON。XML里真正需要关心的不是最外层resultCode而是业务状态位比如“接收状态”为1表示成功、2表示逻辑错误、3表示系统异常。JSON接口的字段匹配可以用System.Text.Json配合强类型DTO也可以用字典方式动态取值。我建议用强类型DTO因为医保返回字段数量有限、结构稳定而字典取值在字段名拼错时编译器不报错运行时才暴露属于典型的“跑起来就翻车”。4.3 重试队列失败文件别丢、重复传要防传输程序的可靠性不体现在正常情况而体现在断网、重启、接口报错这些异常之后。新手最容易犯的错是上传失败直接抛异常然后下一轮轮询又去源目录找文件——如果文件还留在原目录会造成重复上传如果上传成功后另一个环节失败则文件被移走了数据就丢了。我的做法是引入两个“中间状态区”暂存失败文件的pending目录以及记录已完成文件的sent目录。上传前的处理顺序是先从源目录把文件移动到一个带有当前时间戳的pending文件如file_20250914103000.xml再上传到医保端。上传成功后才把pending文件移到sent目录并写台账上传失败则文件留在pending目录下一轮重试。private void ProcessFile(string srcFile) { string pendingFile MoveToPending(srcFile); // 源目录 → pending目录 try { UploadViaSftp(pendingFile, Config.DestDirectory); MoveToSent(pendingFile); // pending → sent RecordLedger(Path.GetFileName(pendingFile), GetFileMd5(pendingFile)); } catch (Exception ex) { Logger.Error(ex, 上传失败留待重试{0}, pendingFile); // 不移动、不删除等待下一轮轮询 } }这个逻辑的核心是“先移动到pending再上传”。就算程序在移动pending文件和上传之间崩溃重启后轮询扫描pending目录也能发现未完成文件恢复处理而源目录中尚未处理的文件不会被清掉。这就让整条链路上不会出现“文件凭空消失”。幂等还有一个层面医保端如果重复收到同一批单据多数会按业务主键去重但也有部分老接口没有去重能力传两次就是两条费用记录。这种接口需要在上传前查询批次是否已在医保端落库一般是调一个查询接口传文件里的结算流水号有记录就跳过。查询接口调用失败时按“未知”处理宁可暂停上传也不要盲传这个分寸需要和医院信息科确认。5. 部署与高频避坑服务装不上、文件传一半、日志撑爆磁盘这一章全部是踩坑记录。如果你照前面章节做完大概率会在部署和运行阶段撞上下面几条。每一条按“现象→原因→解决”写方便你出问题时直接对号入座。5.1 现象服务安装成功但启动后马上停止事件查看器报“服务没有及时响应”原因OnStart里做了耗时的操作比如读取网络共享目录或建立连接导致服务管理器在30秒内没有收到OnStart返回信号。我们之前从计划任务方案迁过来时直接在OnStart里初始化连接结果连接超时20秒再加上其他初始化动作超过了服务管理器的等待阈值服务被误判启动失败。解决OnStart只做两件事创建CancellationTokenSource和启动Worker线程其余所有初始化都放到WorkerLoop的第一次循环里做。protected override void OnStart(string[] args) { _cts new CancellationTokenSource(); _worker new Thread(WorkerLoop); _worker.Start(); }初始化代码挪到WorkerLoop开头private void WorkerLoop() { try { InitializeOnce(); } catch (Exception ex) { Logger.Error(ex, 初始化失败等待下次轮询重试); } while (...) { ... } }这样即使初始化要30秒OnStart也早就返回了。事件查看器不再误判服务状态是“已启动”初始化失败靠日志暴露。5.2 现象上传到医保端的文件解析报“记录超长”或“关键字段缺失”原因HIS端还在写文件时程序就把文件读走了。文件写入不是原子操作HIS往往是先写主体再补尾部汇总行传输程序在尾部汇总行出现前扫到文件就上传医保端解析时少了一行报错千奇百怪。解决只认“写完标记”。常见做法有两个一是HIS导出完成后生成一个同名的哨兵文件比如upload_20250914.done传输程序看到.done才处理同名的数据文件二是靠“连续两次扫描文件大小不变”法但哨兵文件更可靠因为它天然表明业务侧已经写完。如果HIS是第三方系统改不动退而求其次用“最近修改时间距今超过10分钟”这个经验值。private bool IsFileReady(string path) { string doneFile Path.ChangeExtension(path, .done); if (File.Exists(doneFile)) { return true; } var lastWrite File.GetLastWriteTime(path); return (DateTime.Now - lastWrite).TotalMinutes 10; }5.3 现象断网恢复后批量重复上传医保端出现重复结算记录原因上传成功后程序还没把文件移到sent目录就崩溃或者网络层收到ACK但医保端处理超时程序以为失败重传。这个问题的本质是“上传成功”与“本地标记成功”之间没有原子性。解决重复上传无法彻底避免只能靠医保端幂等。所以第4章的复查动作不是可选项。如果医保端没有查询接口那就做一个折中上传前查本地台账台账里标记为“疑似成功”的文件发告警人工确认不要自动重传。针对超时场景把“已发出但未确认”的文件单独放到一个retry目录等超时时间过后先查询查到成功就移走查不到再重传。超时时间按医保接口文档建议的最大处理时间设置不要自己猜。5.4 现象运行半年后C盘爆满医保前置机上的服务全部变慢原因程序用了文件日志默认配置是单文件无限增长。传输程序每天几百行日志看似不多但半年下来能达到几GB。C盘一满数据库、Windows更新全停医保端传不进数据。解决日志一定要按天或按大小滚动。下面这段是NLog按天滚动的最小配置target namefile xsi:typeFile fileName${basedir}/logs/transfer-${shortdate}.log archiveAboveSize5242880 maxArchiveFiles30 layout${longdate}|${level}|${message} /参数说明archiveAboveSize单位是字节5MB一个文件maxArchiveFiles30表示最多保留30个归档文件超过自动删除最旧的。这样日志总大小上限约为150MB无论跑多久都不会撑爆磁盘。5.5 现象上传的TXT文件在医保端打开是乱码或自己下载回来乱码原因两头编码不一致。HIS导出的是GBKFTP上传时按UTF-8读取或转码或者读文件的代码用Encoding.Default在中文系统上是GBK程序部署到英文版Windows上Encoding.Default就变了。解决任何涉及编码的地方都显式指定编码不依赖Encoding.Default。老接口用GBKEncoding.GetEncoding(GBK)新接口强行要求UTF-8时用带BOM的UTF-8写入。在App.config里用EncodingName配置项启动时读取并注册代码页提供程序Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding fileEncoding Encoding.GetEncoding(Config.FileEncodingName);这行注册代码在.NET Framework下可省但在.NET Core和.NET 5环境下如果少了Encoding.GetEncoding(GBK)会抛“不支持此代码页”。我见过很多从.NET Framework迁到.NET 6的旧服务在这里翻车。5.6 现象服务运行账号没有目录权限传一会就报“访问被拒绝”原因服务安装时选了LocalSystem但HIS导出目录在另一台服务器上共享权限只给了域账号或者日志目录位于Program Files下权限收紧后服务无法写日志。解决在Windows服务属性里把“登录”改为“此账户”使用有共享目录和日志目录读写权限的专门服务账号。注意这个账号要勾选“密码永不过期”并在服务属性里重新输入密码否则密码到期后服务直接启动失败。顺带一提密钥如果存在注册表里也要保证这个服务账号对注册表项有读权限。这一章最后补一条经验事件查看器永远是第一排查入口。服务相关报错在“Windows日志→系统”里业务链路日志在程序自己的日志里。不要一上来就怀疑代码先看服务有没有起来、目录权限对不对能省一半时间。6. 进阶把自动传输做成“会自检”的无人值守程序这一章是收尾也可以说是把程序从“能跑”提高到“敢不管”的关键。医保接口传输这个活难的不是代码是没人知道它哪天悄无声息地停了。下面几个做法值得普适到其他服务上。6.1 心跳文件让外部系统确认“我活着”在传输服务里加一个每轮循环写入的心跳文件比如Heartbeat.txt内容为当前时间戳。外部监控程序扫描这个文件若心跳超过N分钟没有更新则发告警。这是用最便宜的方式把服务的运行状态暴露出来。File.WriteAllText(Config.HeartbeatFile, DateTime.Now.ToString(HH:mm:ss));心跳文件的目录要选在没有权限问题的位置比如服务自己的安装目录同时注意心跳和日志不要写到同一个文件否则日志轮转时会干扰心跳判断。6.2 优雅停机当前批次传完再停第3章的CancellationToken只解决“立即停”但在传输场景下粗暴停机容易留下pending半成品文件。我的习惯是OnStop里先进入“排空模式”给WorkerLoop一个宽限时间。protected override void OnStop() { _cts.Cancel(); _worker?.Join(TimeSpan.FromSeconds(30)); }同时在WorkerLoop检测到取消信号后如果当前刚好在传一个文件不中断上传而是把状态标记为“CancellationPending本文件完成后退出”。这个行为和数据库事务里的“提交后退出”是一个思路。6.3 一次对接的教训最后讲一条个人教训。早年我接的一个项目程序在开发机上一切正常部署到医保前置机后每天固定丢一个时段的数据而且日志里没有任何报错。排查了三天最后发现是前置机的系统时间比标准时间慢了4分钟而HIS导出文件名里的时间戳用的是前置机本地时间程序用服务器时间做文件名匹配两边对不上。从那以后我再接任何传输程序都会在初始化代码里显式记录一次“本机时间、对方时间、偏差”放进日志开头。现在我自己写这类服务一定会把心跳、优雅停机、日志轮转和三方时间记录做成默认标配而不是“有空再加”。因为无人值守程序的第一原则不是功能多强大而是出了问题能被人及时发现、快速定位。希望这个方案的骨架和坑位能帮到你省掉几晚上蹲在医院机房调试的功夫。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑