1. 项目背景与需求解析
在医疗信息化系统中,医生日常需要处理大量病历文档的录入工作。HIS(医院信息系统)作为核心业务平台,经常需要处理包含CT影像、化验单等医疗图片的病历内容。CKEditor作为一款成熟的富文本编辑器,被广泛应用于各类医疗系统的前端界面。
实际工作中,医生习惯通过截图工具快速获取检查报告图像,然后直接粘贴到编辑器中。但默认情况下,CKEditor仅支持base64格式的图片内联展示,这会导致:
- 数据库体积急剧膨胀(单张1MB图片base64编码后约1.37MB)
- 病历加载速度明显下降
- 系统备份恢复效率降低
因此需要实现:
- 自动检测粘贴操作中的图片内容
- 将base64图片转为服务器物理文件存储
- 替换编辑器内容为实际图片URL
- 保障医疗数据的安全性和可追溯性
2. 技术方案设计
2.1 整体架构设计
采用前后端分离方案:
code复制[浏览器端CKEditor]
→ [粘贴事件监听]
→ [图片提取上传]
→ [PHP接收接口]
→ [文件存储服务]
→ [返回访问URL]
2.2 关键技术选型
- 前端处理:CKEditor自定义插件 + Paste事件监听
- 图片处理:canvas压缩 + 文件名哈希处理
- 后端存储:PHP + 阿里云OSS(医疗级存储服务)
- 安全措施:
- 文件类型白名单(仅允许jpg/png)
- 病毒扫描接口调用
- 操作日志记录
3. 前端实现细节
3.1 CKEditor插件开发
创建自定义插件uploadimage:
javascript复制CKEDITOR.plugins.add('uploadimage', {
init: function(editor) {
editor.on('paste', function(evt) {
// 检测粘贴内容中的图片
for (let item of evt.data.dataTransfer.getFiles()) {
if (item.type.match('image.*')) {
uploadImage(item).then(url => {
editor.insertHtml(`<img src="${url}">`);
});
}
}
});
}
});
3.2 图片预处理
通过canvas进行尺寸和质量的优化:
javascript复制function compressImage(file) {
return new Promise(resolve => {
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() {
const canvas = document.createElement('canvas');
// 保持比例缩放到最大宽度1200px
const scale = Math.min(1200/img.width, 1);
canvas.width = img.width * scale;
canvas.height = img.height * scale;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// 质量压缩到70%
canvas.toBlob(resolve, 'image/jpeg', 0.7);
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
});
}
4. PHP后端实现
4.1 文件接收接口
php复制<?php
header('Content-Type: application/json');
// 医疗系统需要严格的权限验证
require_once 'auth_check.php';
// 文件类型白名单
$allowedTypes = ['image/jpeg', 'image/png'];
$file = $_FILES['image'] ?? null;
if (!$file || !in_array($file['type'], $allowedTypes)) {
http_response_code(400);
die(json_encode(['error' => 'Invalid file type']));
}
// 生成唯一文件名:日期+MD5+随机数
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$filename = date('Ymd').'_'.md5_file($file['tmp_name']).'_'.mt_rand(1000,9999).'.'.$extension;
// 调用医疗存储服务
$ossClient = new OSS\OssClient(OSS_ACCESS_ID, OSS_ACCESS_KEY, OSS_ENDPOINT);
try {
$result = $ossClient->uploadFile(OSS_BUCKET, 'medical_records/'.$filename, $file['tmp_name']);
echo json_encode(['url' => $result['info']['url']]);
} catch(Exception $e) {
http_response_code(500);
error_log('Upload failed: '.$e->getMessage());
die(json_encode(['error' => 'Upload failed']));
}
4.2 安全增强措施
- 文件内容校验:
php复制// 检查实际文件类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
if (!in_array($mime, $allowedTypes)) {
unlink($file['tmp_name']);
die(json_encode(['error' => 'File type mismatch']));
}
- 病毒扫描(需集成医疗安全组件):
php复制$scanResult = MedicalSecurity::scanFile($file['tmp_name']);
if ($scanResult['risk_level'] > 1) {
die(json_encode(['error' => 'File security check failed']));
}
5. 系统集成方案
5.1 CKEditor配置
javascript复制CKEDITOR.replace('editor', {
extraPlugins: 'uploadimage',
uploadUrl: '/api/upload.php',
// 医疗系统需要禁用外部图片URL
disallowedContent: 'img[src^="http://"]; img[src^="https://"]',
// 图片上传超时设置为60秒
filebrowserUploadTimeout: 60000
});
5.2 病历存储优化
建议数据库存储结构:
sql复制CREATE TABLE medical_images (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
patient_id VARCHAR(32) NOT NULL,
visit_no VARCHAR(32) NOT NULL,
file_path VARCHAR(255) NOT NULL,
upload_time DATETIME DEFAULT CURRENT_TIMESTAMP,
upload_user VARCHAR(32) NOT NULL,
file_size INT NOT NULL,
INDEX idx_patient (patient_id),
INDEX idx_visit (visit_no)
);
6. 医疗场景特殊处理
6.1 敏感数据脱敏
在上传前进行OCR识别和关键信息遮盖:
python复制# 伪代码示例(实际需调用医疗AI服务)
def medical_redact(image):
text = ocr_recognize(image)
sensitive_fields = ['身份证号', '手机号', '病历号']
for field in sensitive_fields:
positions = find_text_positions(text, field)
for pos in positions:
image = draw_rectangle(image, pos, color='black')
return image
6.2 审计日志要求
符合《医疗信息系统安全规范》的日志记录:
php复制// 在上传成功后记录
$logData = [
'user_id' => $_SESSION['user']['id'],
'patient_id' => $_POST['patient_id'],
'action' => 'image_upload',
'file_name' => $filename,
'ip_address' => $_SERVER['REMOTE_ADDR'],
'timestamp' => date('Y-m-d H:i:s')
];
DB::insert('security_logs', $logData);
7. 性能优化方案
7.1 前端优化
- 采用Web Worker进行图片压缩
- 实现分片上传(大尺寸CT影像适用)
- 上传进度条显示
7.2 服务端优化
- 使用Redis做临时缓存
- 配置PHP上传限制:
ini复制; php.ini配置
upload_max_filesize = 20M
post_max_size = 25M
max_execution_time = 300
- Nginx优化配置:
nginx复制client_max_body_size 25M;
client_body_buffer_size 512k;
proxy_read_timeout 300s;
8. 异常处理与监控
8.1 错误分类处理
| 错误类型 | 处理方案 | 用户提示 |
|---|---|---|
| 文件过大 | 中断上传 | "图片需小于20MB,请使用压缩工具" |
| 网络中断 | 自动重试3次 | "网络不稳定,正在重新上传..." |
| 服务异常 | 本地暂存 | "系统繁忙,图片已保存到本地" |
8.2 监控指标
- 上传成功率(需>99.5%)
- 平均处理时间(应<5s)
- 存储空间使用率预警
9. 实际部署经验
9.1 医院环境常见问题
- IE兼容问题:部分老版本HIS系统仍需支持IE11
javascript复制// IE11的Blob polyfill
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function(callback, type, quality) {
const dataURL = this.toDataURL(type, quality);
const binStr = atob(dataURL.split(',')[1]);
const len = binStr.length;
const arr = new Uint8Array(len);
for (let i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i);
}
callback(new Blob([arr], {type: type || 'image/png'}));
}
});
}
- 内网传输限制:有些医院PACS系统与HIS网络隔离,需要特殊代理配置
9.2 性能实测数据
测试环境:100张平均3MB的CT截图批量上传
- 无压缩方案:总耗时182秒
- 启用压缩后:总耗时67秒(压缩率65%)
- 服务器负载下降40%
10. 扩展功能建议
- 智能图床切换:根据网络环境自动选择院内存储或云端存储
- 病历版本对比:上传时自动与历史图片进行差异检测
- OCR集成:自动提取图片中的检验数值填入表单
关键提示:医疗系统上线前必须进行
- 全流程压力测试(模拟200医生同时上传)
- 容灾演练(断网/断电情况下的数据完整性)
- 安全渗透测试(重点防范病历泄露风险)