健康管理类系统听起来门槛不高,真正动手做起来才发现难点密集:设备数据格式五花八门、医疗数据对准确性要求苛刻、健康建议不能瞎给。这套系统从立项到上线迭代了两个版本,服务对象是社区体检中心和慢病管理人群,日均处理设备上报数据约五十万条。下面把这些经验整理出来,重点讲架构设计、数据层处理和智能分析三块,希望能给做类似项目的同行省一些摸索时间。

一、技术选型:为什么最终还是选了.NET
项目初期团队内部争论过Java和C#两条路线。Java生态在医疗行业确实成熟,但团队主力技术栈是C#,且客户部署环境以Windows Server为主,运维同事对IIS更熟悉。综合评估后选择.NET 6作为运行时,Web层用ASP.NET Core Web API,前台管理端用WPF做内网客户端,用户端小程序通过接口对接。这个组合的最大好处是一套C#代码可以在服务端、桌面端复用大量业务逻辑类库。
数据库选型上走了弯路。最初方案是MySQL存全部数据,结果三个月后血压、心率这类高频时序数据把单表撑到两亿多行,普通查询明显变慢。最终调整为混合存储:SQL Server存用户档案、体检报告等业务数据,InfluxDB专门承接设备上报的时序数据,Redis做实时预警的缓存中间件。这次调整说明健康数据必须按访问模式拆分存储,不能图省事一张库包打天下。
ORM层面用的EF Core,配合Pomelo.EntityFrameworkCore.MySql和官方SQL Server驱动。有一点要提醒:EF Core处理超大批量插入性能不佳,设备数据入库环节我们改用了原生批量写入通道,EF Core只负责业务实体,两者分工明确。
二、设备接入与数据清洗:整个系统最脏最累的活
智能健康系统的数据来源极其分散:蓝牙血压计、体脂秤、智能手环、医院HIS系统的体检报告,每家的数据格式、上报频率、单位都不一样。比如血压有mmHg和kPa两种单位,体重设备有的上报公斤有的上报斤。我们在接入层设计了统一的设备协议适配器模式,每种设备实现一个适配器,把异构数据统一转换为标准化的HealthMetric实体。
public interface IDeviceAdapter
{
string DeviceType { get; }
HealthMetric Convert(RawDeviceData raw);
}
public class BloodPressureAdapter : IDeviceAdapter
{
public string DeviceType => "BP_Device";
public HealthMetric Convert(RawDeviceData raw)
{
// 统一血压单位为 mmHg
decimal systolic = raw.Unit == "kPa"
? raw.Systolic * 7.50062m
: raw.Systolic;
return new HealthMetric
{
UserId = raw.UserId,
Type = MetricType.BloodPressure,
Systolic = Math.Round(systolic, 1),
Diastolic = Math.Round(raw.Unit == "kPa"
? raw.Diastolic * 7.50062m
: raw.Diastolic, 1),
MeasuredAt = raw.Timestamp,
Source = DeviceType
};
}
}
清洗环节要处理的脏数据远比预想多。手环在剧烈晃动时会报出心率200以上的野值,体脂秤电量不足时体重会漂移出人体可能范围。我们实现了一套基于滑动窗口的异常值过滤:取该用户近30天同类型数据的中位数和四分位距,超出上下限一定倍数的值标记为可疑,既不直接丢弃(万一是真实的房颤发作数据),也不进入分析模型,而是进入人工复核队列。医疗数据宁可多复核,不能静默丢弃,这是做健康类系统必须守住的原则。
消息流转用的是RabbitMQ。设备网关把原始数据投递到队列,清洗服务消费后分两路:一路写InfluxDB,一路触发实时预警判断。解耦之后,即使某台设备突然高频上报导致流量洪峰,清洗服务可以水平扩容,数据库写入压力也可控。
三、智能分析:规则引擎加轻量模型的双层方案
所谓智能,第一层其实是规则。慢病管理场景中大部分判断有明确的医学标准:高血压分级、空腹血糖异常、BMI超标等。我们自研了一个简单的规则引擎,规则以JSON配置存储在数据库中,运营人员通过后台界面即可调整阈值,无需发版。规则引擎核心是一个表达式求值器,底层借助DynamicExpresso这个库,把配置的字符串表达式编译成委托,单次求值耗时在微秒级。
var interpreter = new Interpreter()
.SetVariable("systolic", metric.Systolic)
.SetVariable("diastolic", metric.Diastolic)
.SetVariable("age", user.Age);
// 规则示例:收缩压>=160 或舒张压>=100 触发红色预警
var rule = "systolic >= 160 || diastolic >= 100";
bool triggered = interpreter.Eval<bool>(rule);
第二层是趋势分析,这才是系统真正有价值的部分。单次测量值正常不代表安全,连续一周血压缓慢爬升更需要关注。我们用C#实现了简单的线性回归拟合用户近期的测量序列,计算斜率和残差,当斜率持续为正且超过阈值时,推送趋势预警给签约医生。深度学习模型没有自己训练,而是接入了第三方AI分析服务,通过HTTP接口把脱敏后的数据序列传过去获取风险评估结果,C#侧只做结果的落库和展示,这样规避了团队缺乏算法人才的短板。
健康报告生成是最后的输出环节。每周自动汇总生成PDF报告,技术方案是Razor模板渲染HTML再通过Puppeteer Sharp转PDF。这里有个实战细节:服务器若缺少对应依赖库,Puppeteer Sharp会启动失败,Linux容器部署时需要额外安装chromium依赖,建议在Dockerfile里提前处理,不要等到上线才发现。
四、踩过的坑与性能优化
回头看,几个印象深刻的坑值得记录。第一个是并发下的预警重复推送:同一用户短时间内多次触发同一规则,导致医生端收到连环轰炸。解决办法是在Redis中记录规则触发的冷却时间戳,同一条规则对同一用户在冷却期内只推送一次,代码量不大但效果立竿见影。
第二个坑是隐私合规。健康数据属于敏感个人信息,数据库中身份证号、手机号必须加密存储,我们用AES加密字段,密钥托管在独立的密钥服务中而非写死在配置文件。日志输出时统一经过脱敏中间件,防止敏感信息被打进日志文件。这类问题上线前处理好是加分项,被监管查出来就是事故。
性能方面,升级到.NET 6后启用了Server GC并做了JIT分层编译,接口平均响应时间从120毫秒降到40毫秒左右;InfluxDB按月分bucket,历史数据自动降采样,查询近一年趋势图依然流畅。整体来说,C#在健康管理系统这类中大型业务系统中表现稳定,生态虽然不如Java丰富,但开发效率和语言表达力有明显优势,关键组件社区也都有成熟方案。如果项目涉及医疗级诊断功能,记得提前确认合规资质边界,分析建议类输出要留好免责声明,这一点技术之外但同样重要。