恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

EDC系统完整源码解析:从数据模型到审计追踪的临床数据管理

  • 首页
  • 资讯中心
  • /
  • EDC系统完整源码解析:从数据模型到审计追踪的临床数据管理

相关资讯

behave框架实战:用Python BDD让测试聚焦业务行为 2026/10/20 16:46:54
Python AI编程入门指南:机器学习、深度学习、NLP与强化学习四大方向全解析 2026/10/20 16:46:54
软考高项备考三个月一次通过:综合知识、案例分析与论文实战全攻略 2026/10/20 16:46:54

最新资讯

前端上下文模式指南:从透传地狱到Provider共享的工程实践
Vue3自定义Hooks完全指南:设计思路、实现与工程化实践
2026 Agent工程师面试10大核心考点:从任务规划到安全防护
拆解开源Agent框架:插件化设计的核心机制与工程实践
自考论文降AI率实战:智能体改写与人工复核全流程
Docker部署Elasticsearch与Kibana:从环境准备到Compose编排实战

今日推荐

高频变压器实战经验总结:磁芯选型、绕组工艺与故障排查全解析
U-Boot移植的关键:深入理解Kconfig配置系统与依赖关系
步进电机原理与调试全解析:从步距角到丢步排查

本周热门

基于GMM的语音识别系统原理与MATLAB实现详解
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

EDC系统完整源码解析:从数据模型到审计追踪的临床数据管理

发布时间:2026/10/20 16:46:54
EDC系统完整源码解析:从数据模型到审计追踪的临床数据管理 简介在临床研究中纸质数据采集方式常伴随效率低、易出错与核查困难等问题而EDC系统正是解决这一系列难题的核心工具。这套EDC系统完整源代码面向临床试验软件开发者、数据管理人员及希望构建自有数据采集平台的技术团队提供了一套可运行的Java实现。压缩包共6个文件包含4个Java源文件与2个scc辅助文件整体仅17KB代码轻量、结构清晰。源码涵盖临床试验项目管理、CRF病例报告表在线设计、数据录入、疑问管理、逻辑自动校验、CRF与DCF导出PDF、统计报表以及自定义查询等核心环节尤其便于研究者自定义实验流程与数据验证规则。目前已有1729人学习下载既能帮助入门者理解EDC系统的分层架构与业务流转也为有二次开发需求的人员提供了直接可参考的代码基底。1. EDC系统完整源代码先说清它到底解决临床研究里的什么问题做过临床数据管理的人都明白EDC系统最难的不是把表单做成网页而是让每一格数据经得起稽查。临床试验里的数据采集过去靠纸质CRF填写随意、修改无痕、多中心汇总全靠人工录入一个数字写错可能要等统计分析阶段才能暴露。EDC把整个过程变成一条可校验、可追踪、可锁定的数据流填写的每一格都有校验规则每一次修改都有记录每一个质疑都有流转状态。拿到这套完整源代码核心价值不在“有哪些页面”而在它能不能撑起数据完整性、审计追踪和多中心权限隔离这三件事。适合做医疗信息化的开发者、初创CRO的技术负责人以及想自建EDC平台的某临床研究团队——读懂并改造这套源码比从零开始省下至少半年的建模和踩坑时间。2. 拆解EDC系统核心模块数据模型和表单引擎决定代码量的半壁江山2.1 从纸质CRF到eCRF数据模型怎么设计才能撑起多中心研究纸质CRF的层级是研究方案→中心→受试者→访视→表单→填写项。EDC系统要复现这套结构数据库设计必须按同样的层级拆。最常见的做法是用六张核心表分别对应研究、中心、受试者、访视、表单定义和填写值而不是把所有填写内容塞进一张大宽表。研究study和中心site必须分离。一个研究可能同时有七八个中心在入组同一个表单在A中心和B中心可能启用不同字段。把研究属性和中心属性拆开后续做中心级权限、中心级表单配置才有落点。受试者表挂在中心下面用研究中心受试者编号做唯一约束避免多中心编号重复导致的数据串组。字段值不建议直接存“男/女”这类显示文本推荐存编码值加一份字典表。这个我们放到2.3节展开。先看整体表清单四张核心表的设计能覆盖绝大多数EDC应用场景表名作用域说明study研究级研究方案、分期、状态、开始结束时间site中心级中心编号、机构名称、启用状态subject受试者级随机号、入组日期、生命状态form_def访视表单级存储eCRF的JSON定义含字段、校验、跳题逻辑record_value数据实例每个受试者每个访视每个表单的一条填写记录值存JSONBaudit_log数据实例字段级修改前值与修改后值审计追踪只认这张表受试者表要冗余存一个当前访视序号业务上叫visit_seq。为什么冗余因为EDC的访视跳转必须受控受试者没完成V2访视的必填表单系统要禁止直接跳到V3。这个状态如果每次查询时现算访问量和计算开销都高冗余字段配合状态机更稳。2.2 动态表单引擎用JSON Schema存表单定义避免为每个访视写死页面每个临床研究的CRF都不一样同一个研究的访视2和访视3表单也可能完全不同。如果前端为每个表单写一个页面组件新研究一启动就要改代码这是所有EDC源码里最拖后腿的写法。可靠的方案是做动态表单引擎表单结构存JSON后端负责解析和校验前端负责按JSON递归渲染。一份eCRF定义用JSON描述结构是form加fields数组每个field声明字段编码、显示名、数据类型、是否必填、候选值和校验规则。下面是最小可用示例{ form_code: VS_ECG, form_name: 十二导联心电图, version: 1.0, visit_code: V2, fields: [ { field_code: ECG_DONE, field_label: 是否完成心电图检查, data_type: radio, required: true, options: [ { code: 1, label: 是 }, { code: 0, label: 否 } ] }, { field_code: ECG_HR, field_label: 心率次/分, data_type: number, required: true, unit: bpm, validation: { range: [30, 250] } }, { field_code: ECG_NOTE, field_label: 异常描述, data_type: text, required: false, skip_logic: { condition: ECG_DONE 1, explain: 仅当完成检查且结果为异常时填写 } } ] }这个JSON是form_def表的payload字段内容。解释一下关键设计field_code是全表单唯一编码后续的跳题逻辑、质疑生成、审计日志都引用它data_type决定前端渲染控件和后端存值类型validation里的range只是字段级校验的入口真正复杂的跨字段校验在2.4节的规则引擎里。表单版本必须跟着存。表单定义一旦发布填写中的数据不能直接用新定义渲染否则字段编码变了、取值范围变了旧数据对不上。常见做法是form_def表带version字段发布后生成新版本受试者已录入的版本保持不变新受试者用新版本。这条后面避坑章节还会细讲。2.3 数据字典与编码值为什么不直接存“男/女”而要存“1/2”如果直接在数据库里存“男/女”这种中文文本统计阶段就等着翻车。EDC的填充分两种一种来自标准化字典比如性别、种族、是否吸烟另一种来自自由文本比如不良事件描述、合并用药名称。字典类字段的标准做法是存编码值显示文本通过字典表在渲染时映射。设计一套data_dict表结构是dict_type加code加label加sort_order。性别字典就是dict_typeGENDERcode1对应label男。表单定义里radio/select的options直接引用dict_type不写死label。这样换成英文界面、或者统计端要求label按某标准术语集输出时只改字典表不用动已填写的业务数据。统计分析阶段SAS程序员最怕的一件事A中心的“是”存成了YB中心的“是”存成了1合并数据集时全部要手工清洗。用统一编码值就是为了规避这个问题。凡是带options的字段所有中心必须拉同一份字典如果研究允许中心扩展某些选项扩展部分必须走申请流程由数据管理员在系统里开放不能让中心自己随便加字段值。2.4 校验规则引擎SAS风格校验表达式的设计与实现字段级校验比较简单范围、正则、必填。真正难的是跨字段和跨表单校验比如“心电图结果选异常时异常描述必填”或者“不良事件开始日期不能晚于研究中心关闭日期”。这类规则不能写死在Java代码里必须支持配置化。常见做法是定义一套轻量表达式语法字段引用用字段编码操作用、!、、、、||后端用表达式解析器在保存时执行校验。下面是一段表达式执行的核心逻辑的Java简化实现public class EvalRule { // 用简单递归下降解析表达式避免引入重规则引擎 public boolean eval(String expr, MapString, Object values) { // expr示例: ECG_DONE 1 ECG_HR 30 expr expr.replaceAll(\\s, ); if (expr.contains()) { String[] parts expr.split(); for (String p : parts) { if (!evalSingle(p, values)) return false; } return true; } return evalSingle(expr, values); } private boolean evalSingle(String expr, MapString, Object values) { String[] parts expr.split(); if (parts.length 2) { Object actual values.get(parts[0]); Object expected typeConvert(parts[1]); return actual ! null actual.equals(expected); } parts expr.split(); if (parts.length 2) { double actual Double.parseDouble(values.get(parts[0]).toString()); double expected Double.parseDouble(parts[1]); return actual expected; } throw new IllegalArgumentException(不支持的表达式: expr); } }逻辑说明这段代码处理组合和单条件两类场景单条件支持和实际工程还要补充!、、、以及字段与字段比较。typeConvert负责把表达式右值转成和字段值一致的类型比如1转Integer日期转LocalDate。注意只支持同一层级字段的引用跨表单引用需要靠form_code前缀例如VS_ECG.ECG_HR 100解析时先按前缀取对应表单的值。参数方面校验规则的每一条都要配message文案因为保存失败时前端要展示给录入员看还要配rule_level分“报错”和“警示”两级报错阻止保存警示允许保存但弹提示。临床数据管理里有很多非硬性校验比如“体重和上次访视差超过20%”用警示级最合适硬报错反而拖慢入组速度。3. 用Spring Boot落地EDC后端完整源代码的最小闭环3.1 搭建工程骨架与数据库表结构PostgreSQL JSONB就是为EDC准备的EDC的填写数据天然是半结构化同一张表单有的中心可能额外填了备注字段有的字段在方案修订后会被弃用。用关系表为每个字段建列改一次方案就要迁移一次表维护成本极高。实际项目里我见过两种路线一种是MySQL存大文本序列化内容另一种是PostgreSQL JSONB。后者有两个优势JSONB支持字段内索引而且更新时能够只改部分键配合GIN索引可以按表单内容检索。建表SQL至少要覆盖研究、表单定义、填写数据和审计日志这四条链路。下面是精简后的核心建表语句CREATE TABLE study ( study_id BIGSERIAL PRIMARY KEY, study_code VARCHAR(40) NOT NULL UNIQUE, study_status VARCHAR(20) NOT NULL DEFAULT DRAFT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE subject ( subject_id BIGSERIAL PRIMARY KEY, study_id BIGINT NOT NULL REFERENCES study(study_id), site_id BIGINT NOT NULL, subject_no VARCHAR(30) NOT NULL, current_visit_seq INT NOT NULL DEFAULT 1, status VARCHAR(20) NOT NULL DEFAULT SCREENING, UNIQUE (study_id, site_id, subject_no) ); CREATE TABLE form_def ( form_def_id BIGSERIAL PRIMARY KEY, study_id BIGINT NOT NULL REFERENCES study(study_id), form_code VARCHAR(50) NOT NULL, version INT NOT NULL DEFAULT 1, payload JSONB NOT NULL, publish_status VARCHAR(20) NOT NULL DEFAULT DRAFT, UNIQUE (study_id, form_code, version) ); CREATE TABLE record_value ( record_id BIGSERIAL PRIMARY KEY, subject_id BIGINT NOT NULL REFERENCES subject(subject_id), form_def_id BIGINT NOT NULL REFERENCES form_def(form_def_id), visit_seq INT NOT NULL, visit_date DATE, values_json JSONB NOT NULL, record_status VARCHAR(20) NOT NULL DEFAULT SAVED, updated_by VARCHAR(50), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE audit_log ( audit_id BIGSERIAL PRIMARY KEY, record_id BIGINT NOT NULL, field_code VARCHAR(50) NOT NULL, old_value JSONB, new_value JSONB, action VARCHAR(20) NOT NULL, operator VARCHAR(50) NOT NULL, operated_at TIMESTAMPTZ NOT NULL DEFAULT now() );逻辑说明form_def以JSONB存表单定义每次改版生成新version这样一个研究下同一表单就有多个版本。record_value表的values_json直接存填写内容的键值对比如{ECG_DONE:1,ECG_HR:75}。为什么要单独存updated_by而不从session里现取审计日志的operator必须记录操作时的身份之后即使账号被禁用历史的操作者信息依然可查。参数说明subject_id上用JSONB字段内值查询需要为高频检索字段建表达式索引例如对values_json-ECG_HR建index否则后期按字段筛选会全表扫。record_status建议按临床数据管理习惯分SAVED已保存、SUBMITTED已提交、LOCKED已锁定锁定的记录只能由数据管理员解锁。3.2 实现eCRF渲染接口把form_def JSON和已填数据拼成视图返回前端前端要渲染一个表单页面需要两方面内容表单定义字段、类型、选项、跳题逻辑和这个受试者当前已填写的值。后端接口做的事就是把form_def的payload和record_value的values_json合并返回。RestController RequestMapping(/api/edc) public class EcrfController { GetMapping(/subjects/{subjectId}/forms/{formDefId}) public ResponseEntityMapString, Object renderForm( PathVariable Long subjectId, PathVariable Long formDefId) { FormDef formDef formDefService.getById(formDefId); RecordValue record recordValueService .findBySubjectAndForm(subjectId, formDefId) .orElse(null); MapString, Object result new HashMap(); result.put(form_definition, formDef.getPayload()); // 已有值合并到定义里前端按 field_code 取默认值 result.put(values, record null ? new HashMap() : record.getValuesJson()); // 记录当前填写的record_id前端保存时回传 result.put(record_id, record null ? null : record.getRecordId()); // 表单级锁定状态锁定后前端所有控件只读 result.put(locked, record ! null LOCKED.equals(record.getRecordStatus())); return ResponseEntity.ok(result); } }逻辑说明这个接口的重点不在代码量而在约定——渲染时后端只负责给数据和状态前端拿到两个JSON自己做合并。locked字段必须后端判断不能只靠前端隐藏按钮临床数据管理里锁定状态是强约束后端不做校验用户绕过前端直接调保存接口就废了。参数说明渲染接口要考虑空表单场景。受试者还没填过这个表单时record是nullvalues返回空Maprecord_id返回null。前端据此把保存按钮从“更新”变成“初次保存”存储走两条不同SQL。另外visit_seq要在渲染时从subject表取禁止前端传防止用户跨访视写数据。3.3 数据保存与稽查轨迹一个AOP切面解决“谁在什么时间改了什么”EDC的审计追踪是整个系统的合规生命线。审核时审计人员会抽查某条记录的某个字段要求看到完整的修改历史原始值、修改后的值、操作人、操作时间。实现方式是在保存数据的方法上切入AOP保存前读取更新前的记录保存后计算字段级差异写入audit_log。Aspect Component public class AuditAspect { private final AuditLogRepository auditRepo; Around(annotation(com.example.edc.core.AuditTrail)) public Object auditRecord(ProceedingJoinPoint pjp) throws Throwable { Object[] args pjp.getArgs(); SaveRecordRequest req (SaveRecordRequest) args[0]; // 保存前查旧值 RecordValue oldRecord recordValueRepository.findById(req.getRecordId()).orElse(null); Object result pjp.proceed(); // 保存后查新值 RecordValue newRecord recordValueRepository.findById(req.getRecordId()).orElse(null); if (oldRecord null) { // 首次保存所有值算新增 newRecord.getValuesJson().forEach((k, v) - auditRepo.save(buildAudit(k, null, v, CREATE, req.getOperator()))); } else { MapString, Object oldValues oldRecord.getValuesJson(); MapString, Object newValues newRecord.getValuesJson(); newValues.forEach((k, newVal) - { Object oldVal oldValues.get(k); if (!Objects.equals(oldVal, newVal)) { auditRepo.save(buildAudit(k, oldVal, newVal, UPDATE, req.getOperator())); } }); // 处理被清空的字段 oldValues.forEach((k, oldVal) - { if (!newValues.containsKey(k)) { auditRepo.save(buildAudit(k, oldVal, null, UPDATE, req.getOperator())); } }); } return result; } }逻辑说明切面拦截带AuditTrail注解的保存方法保存前后各查一次记录。比较时用键逐个比对旧值存在而新值缺失的字段视为清空处理。这里有一个关键边界审计日志只记录业务字段的变更不记录updated_at这类系统字段否则里面全是“系统修改了自己”的噪音。参数说明字段级比较建议用Jackson把JSONB转成Map再比较注意数字类型。比如前端把75.0传回来Jackson可能解析成Double而库里存的是IntegerObjects.equals会认为不一致产生无效审计。解决办法是统一在入库前把数值型JSON值转成BigDecimal再存比较也要基于BigDecimal。这个属于典型的“代码看着没问题审计日志里全是假差异”的坑。3.4 病例状态机与质疑管理数据清洗的第一道闸门EDC里数据不是填完就完事了。一条记录要经历已保存→已提交→已锁定三个阶段锁定后任何修改都要先走解锁流程。状态机控制在后端实现前端只根据状态渲染可操作按钮。质疑Query是EDC特有的数据清洗工具。所有临床机构都会回答“为什么要改这个数据”问一下数据管理员某字段值和原始病历不一致数据管理员发起质疑中心医生答复或修改质疑关闭。质疑必须有独立表query_id、record_id、field_code、question、answer、status、query_type。质疑状态流转OPEN(待答复)→ANSWERED(已答复)→CLOSED(已关闭)如果答复不通过从ANSWERED重新回到OPEN。后端要拦截一个动作数据已锁定的记录被提出质疑后中心医生填写答复此时系统自动解锁该字段医生修改后重新提交质疑状态置为CLOSED。这个流程用状态枚举加一张流转记录表做不要把状态字段存成字符串散在各处。后续表结构要控制好并发——同一时刻只有一条质疑处于待答复状态可以在queried字段上加唯一索引约束防止数据管理员重复发同字段质疑。4. 前端与电子签名从动态渲染到合规填写的最后一公里4.1 用Vue实现动态表单渲染器组件递归渲染与字段联动后端返回的form_def是一个嵌套JSON前端渲染器的核心是写一个自定义组件RenderField根据data_type分发到不同控件radio渲染单选框、number渲染数字输入框、text渲染文本框。这个组件要递归渲染因为复杂表单里可能有“重复组”结构比如合并用药列表一个组里包含药品名称、给药途径、剂量几个字段组本身是数组。碰到group类型组件循环渲染子字段数组并维护每个子项的索引。Vue实现的核心逻辑如下template div template v-foritem in fields :keyitem.field_code component :isresolveComponent(item.data_type) :fielditem :model-valuemodelValue[item.field_code] :disabledlocked v-bindgetComponentProps(item) update:model-valueupdateField(item.field_code, $event) / /template /div /template逻辑说明model-value是当前表单的所有值对象updateField回调负责更新这个对象并触发联动检查。组件分发时注意日期控件和下拉控件的事件签名差异统一在updateField内部做类型适配。锁定时disabled要传给所有子组件防止只读状态被绕过。参数说明Vue的动态组件resolveComponent需要注册所有可能用到的控件类型新增自定义控件时必须在注册表里声明否则后端JSON多一个data_type前端直接白屏。实际工程中建议在渲染器里做未知类型的fallback渲染成警告条而不是让整个页面崩掉。4.2 联动与跳题逻辑隐藏字段不能只靠前端做跳题逻辑常被做成前端行为ECG_DONE选“否”时隐藏ECG_HR输入框看起来省事。但合规的EDC必须遵守一个原则——凡是跳题逻辑前端隐藏只是为了录入体验后端必须用同一份规则执行判定否则编辑时被隐藏的字段值会被随意丢弃。实现上前端在updateField触发时解析form_def里的skip_logic条件匹配则把对应字段的值从待提交数据里剔除并重置为空。后端在保存接口收到values后用同一个表达式引擎重新评估所有带skip_logic的字段把“隐藏却仍有值”的字段清空并计入审计。双向校验的目的只有一个无论用户怎么操作前端最终落库的数据集必须满足逻辑一致性。计算字段也要往前端放一份。比如BMI体重/身高²输入身高体重后前端实时算但真正的BMI存库值必须以后端计算结果为准前端算的值只做展示计算结果在保存时由后端重新算好覆盖前端传值。4.3 保存策略与电子签名何时自动保存何时必须提交临床录入场景的网络很不稳定尤其在医院病区里Wi-Fi断连是常态。最常见的保存策略是“自动保存加手动提交”录入员每30秒或字段失焦时防抖保存一次保存的数据不入正式库只update到record_value并标记record_statusSAVED录入员确认无误后点“提交并签名”此时系统弹出电子签名框输入密码后调用签名验证接口验证通过记录才置为SUBMITTED。电子签名逻辑不能是前端弹个框确认就算完成。后端签名接口要校验三件事密码是否正确、当前记录是否处于可提交状态、是否在有效签名时间段内。签名信息落库信号量记录签名人、签名时间、签名动作提交/修改/解锁、关联记录ID。提交后任何值变更都必须生成一条新的质疑或走解锁申请签名接口本身也纳入审计轨迹。前端的离线缓存要克制。不要在localStorage里缓存一整个表单的长期数据只缓存当前未提交的临时编辑。缓存时要带上record_id和表单版本号重新联网后先校验版本号是否一致不一致则提示刷新页面防止老版本表单的数据覆盖新版本定义。5. EDC系统完整源代码落地避坑5个临床现场跑出来的血泪经验5.1 坑一多中心时区不一致SAE时间记录全部错乱现象某中心录入的严重不良事件开始时间比实际病历记录的时间早了8小时审计阶段差一点被判定为数据不真实。原因排查发现该中心位于不同时区前端用本地时间生成时间戳后端按服务器默认时区解析时间被强制转换了一次。解决系统内所有时间统一存UTC前端时间控件一律传UTC字符串显示时按浏览器时区转换受试者相关的访视日期属于业务日期必须由录入员手动选择年月日禁止用系统时间自动带入。这条在遇到跨国多中心研究时尤其致命。5.2 坑二表单版本升级后已录入数据回填失败现象某研究中期修订了CRF心电图表单新增了一个字段“QTc间期”版本从1.0升到1.1结果老版本已经填好的数据在重新打开时新字段显示空白保存时还被校验规则拦截说必填项缺失。原因前端用新版本定义渲染旧数据旧记录里没有新字段值但新版本的必填校验对全记录生效了。解决表单定义和填写值必须绑定版本——record_value要冗余存form_version保存时以该版本定义执行校验渲染时加载该版本的payload升级版本只影响新记录。核心原则是“一个记录对应一个版本”既不能拿新版本校验旧数据也不能用旧定义渲染新数据。5.3 坑三稽查日志只记了“改了什么”没记“改前改后”现象某次内部模拟稽查审计人员随机抽查了一条记录日志显示字段ECG_HR被修改过但操作时间和修改人都有唯独看不到修改前的值结果没法确认这次修改是否合规。原因早期版本审计日志只存new_value为了省存储后来需要回溯时才发现没有old_value数据完整性链条断了一截。解决审计日志必须存旧值和新值两列且值序列化要保留原始类型。补充一个细节清空字段也要记一条审计很多实现直接把空值忽略导致“值被清掉”这个动作在历史里消失。5.4 坑四数据字典想当然统计口径在统计分析阶段崩掉现象一个多中心研究A中心“是否吸烟”下拉框是“是/否/不吸烟”B中心是“吸烟/不吸烟/未记录”C中心直接文本输入。统计分析阶段SAS程序员拼数据集时发现三家的“不吸烟”对应了三种code只能手工重新映射花了两个星期清洗。原因表单定义里的options绕过了数据字典各中心按自己的习惯配置。解决字段设计阶段先拉一个枚举清单radio和select必须引用统一字典配置禁止在表单JSON里直接写死options如果有中心特殊需求走字典扩展流程扩展的code由数据管理员统一分配任何中心不得自定义。5.5 坑五权限控制只做了角色没做数据隔离现象某中心的研究者登录系统后理论上只能看到本中心受试者数据结果通过直接拼接URL传subject_id就能打开其他中心的受试者记录。原因后端接口只做了登录认证和角色校验没有做数据权限隔离。解决所有查询接口强制带site_id范围数据权限在Service层统一追加过滤条件不能依赖前端传参。建议写一个基类方法查询时必须显式调用withScopeData()由当前登录用户的研究角色决定能访问的中心列表再和查询条件取交集。这个要做到接口层不只在菜单层。6. 把源代码变成可交付的系统验证链路与二次开发技巧6.1 用模拟数据跑一遍完整业务链路验证状态机是否闭环拿到一套EDC源码先不要急着加功能把基础链路跑通再谈扩展。我习惯用SQL造一套最小模拟数据覆盖“中心创建→受试者入组→表单填写→提交→质疑→修改→重新提交→锁定”的全过程。下面是验证用的核心脚本片段-- 模拟验证确认记录状态流转符合预期 INSERT INTO record_value (record_id, values_json, record_status) SELECT 1, {ECG_HR: 78}, SAVED WHERE NOT EXISTS (SELECT 1 FROM record_value WHERE record_id 1); -- 尝试提交已锁定记录预期被拦截 UPDATE record_value SET record_status LOCKED WHERE record_id 1; -- 断言锁定后提交操作应拒绝 DO $$ BEGIN IF EXISTS (SELECT 1 FROM record_value WHERE record_id 1 AND record_status IN (SAVED, SUBMITTED)) THEN RAISE NOTICE 状态异常记录仍处于可提交状态; END IF; END $$;这条脚本验证三类约束初始状态能否正确创建、状态流转是否受控、锁定后是否被接口层拦截。凡是状态机相关改动都应该用这类SQL脚本在CI里做回归防止某次改分支逻辑把锁定校验漏掉。6.2 二次开发优先级和需要预留的扩展点临床数据管理的标准化输出是EDC后端绕不开的一环。建议把导出功能做成模板化先支持常用的数据集格式CSV、SAS传输文件再支持CDISC标准的ODM XML导出。另外两件事值得优先考虑一是与外部系统对接的中心化随机化接口二是不良事件编码的自动映射。前者影响入组效率后者直接影响药物警戒报告时效。这两块可以在源码基础上做插件式扩展定义好接口文档把外部系统对接参数放在配置表里不要写死在代码里。我现在的习惯是任何EDC项目里先跑一遍“录入→质疑→锁定”的干路流程确认状态机和审计链路没问题再看字段级权限和报表。一套源码最贵的地方就是这些看不见的边界规则功能界面再多状态机有漏洞一样不敢过审计。希望这些经验能帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号