简介这是一份面向临床试验数据管理领域开发者与研究者的电子数据采集系统完整源代码覆盖临床试验项目管理、实验设计、病例报告表CRF表单设计、疑问管理、数据录入、逻辑自动校验、CRF与DCF报告导出PDF以及统计报表等核心功能可满足临床研究从方案配置到数据核对的完整流程。压缩包内共6个文件核心由4个Java源文件组成实现输入模块与手动查询模块输入模块负责受试者数据写入手动查询模块支持按条件检索另含2个辅助工程文件整体仅17KB结构精简便于快速定位关键代码。目前已有1728人学习浏览。借助这份代码可以快速理解电子数据采集系统的分层设计与交互流程为二次开发或学习临床数据采集系统提供完整范本尤其适合需要定制试验方案、表单校验规则或查询逻辑的开发者能显著降低从零搭建系统的时间成本。1. 找到一套能落地的EDC临床数据采集系统源码先看它扛不扛得住真实项目做临床研究数据管理的人多少都经历过被Excel折磨的阶段数据散在各个表格里逻辑核查基本靠肉眼SAE严重不良事件记录传了一版又一版最后监查员来稽查时一堆数据口径对不上。如果你正在评估或准备接手一套EDC临床试验电子数据采集系统最核心的问题不是表单向不漂亮而是这套源码能不能覆盖一个真实临床试验项目从建库、录入、核查、质疑到锁库的完整生命周期。这套EDC系统完整源代码解决的正是这个问题——它不是教学用的片段而是把角色权限、CRF设计、数据核查、随机化、电子签名这些关键模块都拆开摆在桌面上的完整交付物适合想二次开发的技术团队也适合需要理解EDC内部机制的数据管理员。在动手拆这套源码之前我先说一个反直觉但很重要的结论评判EDC功能列表排在第二位数据模型与权限体系的完整度才排第一位。很多开发者的做法是先搭前端表单回头再补权限结果就是越补越乱。这套源码的顺序是反过来的——角色和访视先定义好再挂表单数据这也是它值得拆开看的原因。接下来我从源码结构、部署步骤、核心模块和常见坑四个层面把它讲透。2. 源码整体拆解先厘清EDC系统的模块边界与数据流2.1 模块划分从角色权限到CRF设计五个核心子系统打开这套源码的根目录第一件事不是看代码细节而是理解它的模块划分逻辑。整体上这套EDC系统的代码组织围绕五个子系统展开用户与角色权限子系统管理中心管理员、CRC临床协调员、CRA监查员、DM数据管理员、研究者等角色。每个角色的数据权限范围不同比如CRC只能看到自己负责的中心DM能跨中心看数据并执行质疑操作CRA只能只读访问。临床建库子系统这是EDC和普通业务系统最大的区别。要能创建项目、定义访视Visit、设计CRF表单、配置字段级逻辑。临床上叫建库代码里实际上是元数据驱动——表单定义存储在配置表里录入页面是动态渲染出来的。数据录入与管理子系统受试者管理、按访视填写CRF、保存草稿与提交、数据快照。快照是个很关键的设计因为临床数据一旦提交就不能随便改。数据核查与质疑管理子系统包括系统内逻辑核查比如收缩压必须大于舒张压、人工质疑Query的发起、回复、关闭流程。电子签名与审计日志子系统签名记录谁在什么时间改了哪个字段审计日志保存原始值与新值。这是稽查时最重要的证据链。这套源码把这些子系统以可独立部署的模块形式组织起来每个模块内聚度高模块间通过数据访问接口通信。实际开发中如果团队想砍掉随机化功能直接屏蔽对应模块即可不会牵连数据录入核心链路。2.2 数据模型设计为什么EDC需要元数据驱动而非硬编码表单很多新手在这里会犯一个认识上的错误以为CRF表单就是把HTML表单存进数据库。但实际上EDC的CRF需要频繁变更——方案修订后增加一个字段、某中心加了本地实验室检查项——如果硬编码每次改动都要发版。这套源码采用的是元数据驱动架构数据库里存的是表单结构定义而不是渲染好的页面。CRF字段的属性比如字段类型、是否必填、取值范围、依赖的访视、正则表达式都存在设计表里。前端页面拿到元数据后动态渲染输入控件后端做校验时也读取相同的元数据规则。这样做的好处是临床建库人员不需要写代码在管理界面上配置表单结构就能生成新的CRF。核心数据表大致包括以下若干张表名逻辑名职责关键字段project项目基本信息项目编号、方案版本号、状态visit访视定义访视名称、访视顺序、所属项目crf_form表单定义所属访视、表单版本、启动状态field_config字段配置字段key、类型、必填、正则、关联表subject受试者受试者编号、中心ID、随机号data_record数据记录受试者表单实例唯一索引data_value字段值存储record_id、字段key、值、快照版本query质疑记录关联字段、质疑内容、状态对这个设计我的评价是表分得不算最细但它把握住了EDC的核心——data_value表里值为String类型存储字段含义靠field_config解释。这种EAV实体-属性-值模型看起来土但在表单结构频繁变化的场景下最实用。3. 把源码跑起来部署步骤、环境要求与配置细节3.1 环境准备JDK版本、数据库选型与初始化脚本这套源码后端基于Java技术栈前端是主流MVVM框架数据库用的是MySQL也兼容PostgreSQL但需要小改方言配置。初次部署建议直接按它自带的初始化脚本走不要在环境上标新立异。我的建议版本组合如下JDK1.8或11均可推荐11垃圾回收表现更稳定MySQL5.7或8.0推荐8.0注意驱动要升级到8.x对应版本Redis可选。如果只做单机演示可以不开但做集群部署时session共享和验证码缓存依赖它Nginx部署前端静态资源时用部署的第一步是初始化数据库。源码包中通常有多个版本的SQL脚本目录执行顺序有讲究# 进入脚本目录按实际路径调整 cd /opt/edc-sql # 依次执行1.初始化库 2.初始化基础数据(用户/角色) 3.初始化演示项目数据 mysql -uroot -p edc 01_create_database.sql mysql -uroot -p edc 02_init_base_data.sql mysql -uroot -p edc 03_demo_project_data.sql这段命令做了什么第一条创建数据库实例第二条写入最基础的管理员账号和角色权限种子数据第三条插入一个演示用的临床试验项目包含完整的访视和CRF定义方便你登录后直接看到效果。参数说明-u是数据库用户名-p后面可跟密码不推荐明文edc是数据库名实际执行时替换成你的库名。如果03脚本执行报错多半是02的基础数据里ID不一致检查外键关联按顺序重跑即可。3.2 后端配置application.yml里的数据源与文件存储路径后端配置文件在源码的edc-server/src/main/resources/application.yml路径下部署时重点改这几处spring: datasource: url: jdbc:mysql://localhost:3306/edc?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: edc_user password: your_password servlet: multipart: max-file-size: 50MB max-request-size: 200MB edc: upload: path: /data/edc/upload # 附件上传根目录 audit: enabled: true # 审计日志开关建议始终开启配置修改时有两点容易被忽略第一spring.datasource.url里一定要带characterEncodingutf8和serverTimezone参数否则中文乱码、时间差8小时的问题在临床数据录入时直接致命第二edc.upload.path目录必须先创建好并保证运行用户有读写权限不然受试者知情同意书扫描件上传会白屏报错。这个目录建议单独挂数据盘别放系统盘因为临床数据文件增长很快。修改完配置后打包并启动后端mvn clean package -DskipTests cd edc-server/target/ nohup java -Xms2g -Xmx4g -jar edc-server.jar --spring.profiles.activeprod /data/logs/edc-server.log 21 -Xms2g -Xmx4g是初始堆和最大堆设置按照受试者数量和CRF字段多少调整。如果项目里单表单字段超过200个堆建议不低于4g否则并发录入时GC压力大。3.3 前端部署Nginx配置与API反向代理前端是打包好的静态资源目录构建后的产物通常是dist文件夹。部署方式直白但有一个坑必须提前规避——API代理的路径不能配错server { listen 80; server_name edc.example.com; root /data/edc-frontend/dist; index index.html; # 前端路由使用History模式必须配置try_files location / { try_files $uri $uri/ /index.html; } # API反向代理后端接口统一以/api为前缀 location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; client_max_body_size 200m; # 上传附件较大必须同步调大 } }这个配置里try_files那段解决前端路由刷新404的问题属于History路由模式的标配proxy_pass末尾的/会把/api/前缀剥掉再转发给Java后端所以后端Controller里的请求映射不需要带/api前缀。client_max_body_size如果漏了上传大于默认1MB的文件会直接413报错。部署完后访问登录页用初始化脚本里的管理员账号进入系统看到一个带演示项目数据的EDC工作台部署就算成功了。4. 核心模块代码走读建库配置、逻辑核查与电子签名4.1 动态CRF渲染从字段配置到前端表单这个功能是整条EDC链路的地基。我打开源码里负责表单渲染的组件核心逻辑不复杂但很值得学。它读取的是field_config表中的字段配置然后按顺序动态生成表单控件// 动态渲染CRF字段简化后的核心片段 function renderFormFields(formFields) { const container document.getElementById(crf-container); formFields.forEach(field { // 字段类型input/select/date/radio/checkbox const wrapper document.createElement(div); wrapper.className form-group; let control null; switch (field.fieldType) { case date: control input typedate classform-control name${field.fieldKey} >// 逻辑核查服务核心方法代码有删减 public ListQuery executeEditChecks(Long recordId, MapString, Object formData) { ListQuery generatedQueries new ArrayList(); // 获取该表单配置的所有核查规则 ListEditCheckRule rules editCheckRuleMapper.findByFormId(recordId); for (EditCheckRule rule : rules) { String expression rule.getExpression(); // 将表单数据绑定到表达式引擎执行规则求值 boolean flag executeRule(expression, formData); if (flag) { // 规则触发生成质疑 Query query new Query(); query.setRecordId(recordId); query.setFieldKey(rule.getTargetField()); query.setMessage(rule.getErrorMessage()); query.setStatus(QueryStatus.OPEN); generatedQueries.add(query); } } // 批量保存质疑记录 if (!generatedQueries.isEmpty()) { queryMapper.batchInsert(generatedQueries); } return generatedQueries; }这段代码的关键在于expression——它是从规则配置表里读出的表达式字符串比如age 18 consentDate null。executeRule内部用表达式引擎解析并求值规则表达式设计成可配置的所以临床建库时不需要改代码直接在界面上写布尔表达式就行。QueryStatus.OPEN表示质疑已产生但还没发给录入方。整个流程是保存表单数据后立刻跑核查如果触发了规则自动生成质疑并记录关联的字段录入员再次编辑保存时会被提醒。4.3 电子签名与审计日志稽查路径怎么闭环EDC系统的电子签名和普通办公OA的确认按钮完全是两回事。它在合规层面要求记录签名人的唯一标识、签名时间、签名含义如我确认该数据准确完整以及签名的具体内容快照。这套源码里电子签名不是简单的按钮事件而是要走一个签名服务// 电子签名服务签名记录数据快照 public void signRecord(SignRequest request) { // 1. 校验签名人是否有该表单的签署权限 rbacService.checkSignPermission(request.getUserId(), request.getRecordId()); // 2. 生成数据快照保存当前所有字段值序列化JSON String snapshot dataValueMapper.selectSnapshotJson(request.getRecordId()); // 3. 插入签名记录 SignRecord record new SignRecord(); record.setRecordId(request.getRecordId()); record.setSignerId(request.getUserId()); record.setSignAction(SIGN_AND_LOCK); record.setSnapshot(snapshot); record.setSignTime(new Date()); signRecordMapper.insert(record); // 4. 变更表单状态为已签名锁定编辑 formInstanceMapper.updateStatus(request.getRecordId(), FormStatus.SIGNED); // 5. 写入审计日志 auditLogService.log(request.getUserId(), SIGN, request.getRecordId(), snapshot); }这段代码里有三个细节值得注意第一签名前要生成快照如果以后数据被质疑要求修改修改前后的快照都要留痕第二签名操作绑定权限校验不是所有角色都有签名权限通常只有研究者或PI有这个操作入口第三签名后表单状态变为SIGNED编辑接口会拦截必须走取消签名流程才能更改数据——这个流程在真实场景里很常见源码头一次质检要发起质疑让录入方修改数据就必须走解锁流程。部署时如果遇到已经签名但还能编辑的情况多半是状态流转没配好或者前端按钮没有按状态隐藏。5. 避坑与常见问题从部署到临床数据录入的四个典型故障5.1 初始化脚本执行报错外键约束不通过现象执行03_demo_project_data.sql时中途报错Cannot add or update a child row: a foreign key constraint fails导致演示项目数据不完整。原因02脚本和03脚本之间存在数据ID依赖。如果02脚本执行时某张表的自增ID发生了变化比如之前跑过失败的部分脚本03脚本中硬编码的关联ID就对不上了。解决最稳妥的办法是把三个脚本放在一个事务里执行或者干脆清库重来。检查information_schema.TABLES确认三张核心表里的数据量如果project表有数据但visit表为空直接重新导入03脚本即可不需要重跑02。5.2 前端登录后接口全部返回404现象登录页正常进入但登录成功后工作台数据加载失败浏览器控制台显示大量POST /api/auth/login 404。原因Nginx的proxy_pass配置没写好。常见两种错误一是把http://127.0.0.1:8080/写成了不带末尾斜杠的版本导致路径拼接不对二是后端ContextPath配置了/edc但Nginx代理时没有保留前缀。解决在Nginx配置里保留/api/前缀传递后端在Controller上加统一前缀或者在proxy_pass后面不带斜杠直接透传。从实践看更推荐前者因为后端代码里Controller的Mapping逻辑更清晰也方便日后调整接口版本号。5.3 保存CRF数据时提示数据版本冲突请刷新后重试现象两个录入员在不同浏览器同时打开同一受试者的同一个表单A保存成功后再保存时B的页面提示版本冲突。原因这套源码在data_record表里有一个version字段乐观锁机制。每次保存前取出version值保存时做update ... where version ?如果版本号不匹配影响行数为0就判断为并发冲突。解决不要拿这个功能去和Oracle的悲观锁比较这在EDC场景里是刻意的设计——保护数据变更可追溯。实际使用中务必在页面上实现离开页面自动释放锁定或者保存前弹窗提示当前版本时间。如果业务上经常出现冲突可以缩短前端自动刷新频率比如每2分钟刷新一次表单状态。5.4 上传受试者文件时提示413 Request Entity Too Large现象知情同意书扫描件一般几MB到几十MB上传失败Nginx返回413错误。原因Nginx默认client_max_body_size是1MB该参数在编译时默认值很小不显式配置就会被限制住。解决在server或location块中配置client_max_body_size 200m;同时检查后端Spring配置中的spring.servlet.multipart.max-file-size是否同步调整。两个配置缺一不可因为请求先到Nginx再到Tomcat任何一层卡住都上传不了。6. 进阶用法与验证方法把演示数据跑通后如何验证这套EDC的质量源码跑通、录了几条演示数据不代表你有能力判断它能不能上真实项目。我习惯在接手任何一套EDC源码时做三件事建一个模拟的新项目从头走一遍完整流程再造一些脏数据去看核查和质疑机制的反应最后抽查审计日志的完整性。这套源码在这三关的表现基本能判断它的真实水平。第一件事是建新项目。登录管理员账号进项目中心新建一个模拟项目建议选II期临床试验模板这套源码内置了常见的肿瘤项目模板把访视结构配成筛选期-基线期-治疗期-随访期然后设计一个包含10个字段的简单CRF受试者编号、年龄、性别、身高、体重、收缩压、舒张压、不良事件描述、是否SAE、备注。保存后录入一个虚拟受试者的数据从创建受试者-分配随机号-录入访视数据-提交表单走完。这个流程能暴露的问题主要是CRF配置界面的字段类型是否够用、访视顺序能否调整、提交后还能不能改。第二件事是造脏数据验证核查规则。回到字段配置里加一条规则diastolicPressure systolicPressure时提示舒张压不能大于或等于收缩压。然后录入时故意把数值写反保存后注意观察是否弹出了质疑。重点检查质疑的状态是否从OPEN变为REPLIED以及当录入方点击确认修改后原始值是否有快照记录。如果这套源码连质疑-回复-关闭的闭环都没通说明核心链路还没做完。第三件事是查审计日志。用管理员身份查看刚才录制的数据记录的审计细节检查是否记录了修改前后的字段值、修改人、修改时间、IP地址。这是稽查审核的一部分在正式项目中审计日志断档意味着数据不被采信。实测中这套源码的审计日志覆盖面比较全唯一需要注意的是如果想做数据回滚它只支持看日志、不支持一键回滚——这在设计上是合理的因为临床数据不允许撤销操作但要在运维制度里明确需要回滚时走人工修正流程。另外一个实际操作中很有用的技巧是写一个小脚本批量生成测试数据来压测逻辑核查性能。比如模拟导入2000个受试者的基线访视数据调用保存接口时计一下时# 用curl批量提交模拟数据测试接口耗时示例 curl -X POST http://localhost:8080/api/data/save \ -H Content-Type: application/json \ -H Authorization: Bearer token \ -d { recordId: 10001, formData: { subjectNo: SUBJ001, diastolicPressure: 80, systolicPressure: 120, visitDate: 2024-06-20 } } -w 耗时: %{time_total}s\n这个命令中Authorization头的token要换成登录接口拿到的真实令牌recordId是你要提交数据的记录ID可以先查库拿一个测试用的-w参数输出总耗时。如果单条保存接口在2000条数据压测下平均耗时超过1秒就要检查是不是逻辑核查的表达式解析过于频繁或者ORM的批量插入没有走batch模式。用过这套源码之后最大的体会是EDC系统的复杂度不在单个功能上而在功能之间的状态流转和权限边界——一个字段从编辑中到已提交再到已签名每一步都有不可逆的约束。从那以后我每接到一套新的临床系统源码都强制自己先走一遍建项目-建访视-配表单-录数据-发质疑-签名的主链路任何环节卡住就立刻定位不等部署完再排查。希望这套拆解能帮你在评估和落地EDC系统时少走一些弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?