> 技术文档 > 基于dcmtk的dicom工具 第九章 以json文件或sqlite为数据源的worklist服务(附工程源码)

基于dcmtk的dicom工具 第九章 以json文件或sqlite为数据源的worklist服务(附工程源码)


文章目录

  • 前言
  • 一、CWLServer类介绍
    • 1.1 dcmtk中worklist scp的程序流程
    • 1.2 日志函数
    • 1.3. 服务初始化、启动、停止、状态查询、设置日志级别
    • 1.4 设置数据源
    • 5. 主线程与工作线程
  • 二、数据源类
    • 2.1 XgDataSource类
    • 2.2 WlmDataSourceJson类
    • 2.3 WlmDataSourceSqlite类
  • 三、工程源码

前言

worklist服务是为dicom设备提供查询检查登记信息的服务,是RIS系统与放射科检查设备非常重要的沟通桥梁。
基于dcmtk实现worklist服务,支持从json文件或sqlite3数据库中读取登记信息。
本章介绍从json文件中读取登记信息,根据设备发送的查询条件,把登记信息发送到设备。
参考dcmtk源码项目:wlmscpfs,文件:dcmtk-3.6.9\\dcmwlm\\apps\\wlcefs.cc,dcmtk-3.6.9\\dcmwlm\\apps\\wlmscpfs.cc,dcmtk-3.6.9\\dcmwlm\\libsrc\\wlmactmg.cc
程序界面参考基于dcmtk的dicom工具 第三章 图像接受StoreSCP(1),基本一样
用第七章 FindSCU-查询工作列表做客户端测试,效果如下:
在这里插入图片描述


一、CWLServer类介绍

CWLServer类与第四章 图像接受StoreSCP(2)中CStoreServer类结构一致。

1.1 dcmtk中worklist scp的程序流程

dcmtk中scp的流程基本一致,注意与第四章 图像接受StoreSCP(2) storescp的流程对比。

  1. ASC_initializeNetwork初始化网络,整理放入到主线程函数DoNetWorks
  2. acceptAssociation等待连接,整理放入到主线程函数DoNetWorks
  3. 在acceptAssociation中调用ASC_receiveAssociation接受连接,接受到连接后,新建任务放入到线程池由工作线程处理
  4. 服务停止DoNetWorks中调用ASC_dropNetwork关闭网络
  5. 工作线程函数DcmWorkThread->DealAssociation
  6. 重点函数DealAssociation,
    1. 调用ASC_acceptContextsWithPreferredTransferSyntaxes,
      设置接受Verification SOP Class,允许echoscu;
      设置接受抽象语法UID_FINDModalityWorklistInformationModel,允许接受CFind-RQ;
      设置支持的传输语法 transfer Syntaxes,本项目接受所有支持的语法\"we accept all supported transfer syntaxes\";
    2. 调用acceptUnknownContextsWithPreferredTransferSyntaxes,设置接受其他未知的Storage SOP Class
    3. 调用ASC_getApplicationContextName获取协商结果
    4. 调用ASC_acknowledgeAssociation通知连接成功
    5. 调用processCommands处理命令,支持C-ECHO-RQ 和 DIMSE_C_FIND_RQ两种命令。
    6. processCommands中调用DIMSE_receiveCommand接受命令,根据命令类型分别调用echoSCP和HandleFindSCP处理。
    7. 重点HandleFindSCP中调用DIMSE_findProvider,传入从WlmDataSource派生的类的实例做参数,本章从json中读取检查信息,类名为WlmDataSourceJson,后面详细介绍该类。
    8. 图像接受完成调用ASC_dropSCPAssociation,ASC_destroyAssociation释放连接

1.2 日志函数

参考 第四章 图像接受StoreSCP(2)中CStoreServer类

1.3. 服务初始化、启动、停止、状态查询、设置日志级别

参考 第四章 图像接受StoreSCP(2)中CStoreServer类

1.4 设置数据源

数据源支持json文件和sqlite3数据库文件,在HandleFindSCP函数中根据文件后缀名决定实例化哪个数据源类来处理数据源。

class CWLServer{public:CWLServer();~CWLServer();...// 支持从json文件、sqlite3数据库void SetSource(std::string source);int GetState() {return m_state;}private:std::string m_source;}void CWLServer::SetSource(std::string source){m_source = source;}OFCondition CWLServer::HandleFindSCP(T_ASC_Association* pAssoc, T_DIMSE_C_FindRQ *request, T_ASC_PresentationContextID presID){// Create callback data which needs to be passed to DIMSE_findProvider later.WlmFindContextType context;context.priorStatus = WLM_PENDING;// 根据文件后缀名决定实例化哪个数据源类来处理数据源XgDataSource* pWlmDataSource = nullptr;std::string ext;ext = m_source.substr(m_source.rfind(\'.\'));if (ext == \".json\") {pWlmDataSource = new WlmDataSourceJson(this);}else {pWlmDataSource = new WlmDataSourceSqlite(this);}...if (pWlmDataSource){delete pWlmDataSource;pWlmDataSource = NULL;}return cond;}

5. 主线程与工作线程

参考 第四章 图像接受StoreSCP(2)中CStoreServer类

二、数据源类

在1.1 dcmtk中worklist scp的程序流程中,6.7 HandleFindSCP中调用DIMSE_findProvider,传入从WlmDataSource派生的类的实例做参数。
需要从WlmDataSource派生类来从数据源中加载登记信息。只需要重写以下五个函数,函数作用看注释。

OFCondition ConnectToDataSource(); // 连接数据源OFCondition DisconnectFromDataSource(); // 关闭数据库OFBool IsCalledApplicationEntityTitleSupported(); // 白名单判断客户端是否允许查询WlmDataSourceStatusType StartFindRequest(const DcmDataset &findRequestIdentifiers); // 查询数据DcmDataset *NextFindResponse(WlmDataSourceStatusType &rStatus); // 发送数据

因为要实现从json文件和sqlite3数据库中读取登记信息,且IsCalledApplicationEntityTitleSupported,NextFindResponse两个函数可能共用,所以把能共用的代码提取公共类XgDataSource,这个类从WlmDataSource派生。XgDataSource中重写IsCalledApplicationEntityTitleSupported,NextFindResponse两个函数。

  1. 从XgDataSource中派生WlmDataSourceJson类,重写ConnectToDataSource,DisconnectFromDataSource,StartFindRequest三个函数,实现从json文件中读取登记信息。
  2. 从XgDataSource中派生WlmDataSourceSqlite类,重写ConnectToDataSource,DisconnectFromDataSource,StartFindRequest三个函数,实现从sqlite3数据库文件中读取登记信息。

2.1 XgDataSource类

重点是重写的NextFindResponse函数,发送数据到客户端,需要发送哪些字段,
参考DICOM3.0协议第四章第190页, PS 3.4 Service Class Specifications -> K.6.1.2.2
Modality Worklist Attributes -> Table K.6-1
基于dcmtk的dicom工具 第九章 以json文件或sqlite为数据源的worklist服务(附工程源码)
基于dcmtk的dicom工具 第九章 以json文件或sqlite为数据源的worklist服务(附工程源码)
基于dcmtk的dicom工具 第九章 以json文件或sqlite为数据源的worklist服务(附工程源码)

头文件:

#pragma once#include \"dcmtk/config/osconfig.h\"#include \"dcmtk/dcmdata/dcfilefo.h\"#include \"dcmtk/dcmdata/dctk.h\" #include \"dcmtk/dcmwlm/wlds.h\"#include \"dcmtk/dcmwlm/wlfsim.h\"#include \"Defines.h\"class CWLServer;class XgDataSource : public WlmDataSource{public:XgDataSource() {};XgDataSource(CWLServer* server) : m_pServer(server) {};virtual ~XgDataSource() {};void SetParams(std::string peerAET, std::string peerIP, std::string dataSource) {m_peerAET = peerAET;m_peerIP = peerIP;m_source = dataSource;}protected:// 必须实现的父类虚函数OFBool IsCalledApplicationEntityTitleSupported() override;DcmDataset *NextFindResponse(WlmDataSourceStatusType &rStatus) override;void HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(DcmDataset *dataset, const DcmTagKey &sequenceTagKey);protected:CWLServer* m_pServer;std::list<WLINFO>m_matchingDatasets;std::string m_peerAET;std::string m_peerIP;std::string m_source;};

源文件:

#include \"pch.h\"#include \"XgDataSource.h\"#include \"Utilities.h\"#include \"CWLServer.h\"void XgDataSource::HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(DcmDataset *dataset, const DcmTagKey &sequenceTagKey){DcmElement *sequenceAttribute = NULL, *referencedSOPClassUIDAttribute = NULL, *referencedSOPInstanceUIDAttribute = NULL;// in case the sequence attribute contains exactly one item with an empty// ReferencedSOPClassUID and an empty ReferencedSOPInstanceUID, remove the itemif (dataset->findAndGetElement(sequenceTagKey, sequenceAttribute).good() &&((DcmSequenceOfItems*)sequenceAttribute)->card() == 1 &&((DcmSequenceOfItems*)sequenceAttribute)->getItem(0)->findAndGetElement(DCM_ReferencedSOPClassUID, referencedSOPClassUIDAttribute).good() &&referencedSOPClassUIDAttribute->getLength() == 0 &&((DcmSequenceOfItems*)sequenceAttribute)->getItem(0)->findAndGetElement(DCM_ReferencedSOPInstanceUID, referencedSOPInstanceUIDAttribute, OFFalse).good() &&referencedSOPInstanceUIDAttribute->getLength() == 0){DcmItem *item = ((DcmSequenceOfItems*)sequenceAttribute)->remove(((DcmSequenceOfItems*)sequenceAttribute)->getItem(0));delete item;}}OFBool XgDataSource::IsCalledApplicationEntityTitleSupported(){return OFTrue;}DcmDataset * XgDataSource::NextFindResponse(WlmDataSourceStatusType &rStatus){WLINFO wlInfo;if (m_matchingDatasets.size() == 0){//log_debug(LT_DICOM, \"find datasource size=0,return success.\");DisconnectFromDataSource();rStatus = WLM_SUCCESS;return NULL;}else{//if (m_pWlCfg->bReverseSend)//wlInfo = m_listWLResult.front();//elsewlInfo = m_matchingDatasets.front();}DcmDataset *pWLDataSet = new DcmDataset();OFCondition cond;//字符集std::string strCharSet = \"ISO_IR 100\";// 字符集pWLDataSet->putAndInsertString(DCM_SpecificCharacterSet, strCharSet.c_str());// 检查部位/* create Scheduled Procedure Step Sequence (0040,0100) */DcmItem *pScheduledProcedureStepSequenceItem = NULL;cond = pWLDataSet->findOrCreateSequenceItem(DCM_ScheduledProcedureStepSequence, pScheduledProcedureStepSequenceItem, -2);COleDateTime regDate;regDate.ParseDateTime(wlInfo.RegistyDate.c_str());if (pScheduledProcedureStepSequenceItem && cond.good()){pScheduledProcedureStepSequenceItem->putAndInsertString(DCM_ScheduledStationAETitle, m_peerAET.c_str());if (regDate.GetStatus() == COleDateTime::valid){pScheduledProcedureStepSequenceItem->putAndInsertString(DCM_ScheduledProcedureStepStartDate, regDate.Format(_T(\"%Y%m%d\")));pScheduledProcedureStepSequenceItem->putAndInsertString(DCM_ScheduledProcedureStepStartTime, regDate.Format(_T(\"%H%M%S\")));}if (!wlInfo.Modality.empty()){pScheduledProcedureStepSequenceItem->putAndInsertString(DCM_Modality, wlInfo.Modality.c_str());}// 检查技师pScheduledProcedureStepSequenceItem->putAndInsertString(DCM_ScheduledPerformingPhysicianName, wlInfo.PerformingPhysician.c_str());// 检查部位pScheduledProcedureStepSequenceItem->putAndInsertString(DCM_ScheduledProcedureStepDescription, wlInfo.ExamItem.c_str());pScheduledProcedureStepSequenceItem->putAndInsertString(DCM_ScheduledStationName, \"\");pScheduledProcedureStepSequenceItem->putAndInsertString(DCM_ScheduledProcedureStepLocation, \"\");pScheduledProcedureStepSequenceItem->putAndInsertString(DCM_PreMedication, \"\");pScheduledProcedureStepSequenceItem->putAndInsertString(DCM_ScheduledProcedureStepID, wlInfo.StudyId.c_str());pScheduledProcedureStepSequenceItem->putAndInsertString(DCM_RequestedContrastAgent, \"\");}pWLDataSet->putAndInsertString(DCM_RequestedProcedureID, wlInfo.StudyId.c_str());pWLDataSet->putAndInsertString(DCM_RequestedProcedureDescription, wlInfo.ExamItem.c_str());/* create Requested Procedure Code Sequence (0032,1064) */DcmItem *pRequestedProcedureCodeSequencePart = NULL;cond = pWLDataSet->findOrCreateSequenceItem(DCM_RequestedProcedureCodeSequence, pRequestedProcedureCodeSequencePart, -2);if (pRequestedProcedureCodeSequencePart && cond.good()){pRequestedProcedureCodeSequencePart->putAndInsertString(DCM_CodeValue, \"0\");pRequestedProcedureCodeSequencePart->putAndInsertString(DCM_CodingSchemeDesignator, \"0\");pRequestedProcedureCodeSequencePart->putAndInsertString(DCM_CodingSchemeVersion, \"0\");pRequestedProcedureCodeSequencePart->putAndInsertString(DCM_CodeMeaning, wlInfo.ExamItem.c_str());}// 生成Study Instance UIDstd::string studyInsUID;FormatStr(studyInsUID, \"1.2.840.122619.2.5.4421578.260.666.%s\", wlInfo.StudyId.c_str());pWLDataSet->putAndInsertString(DCM_StudyInstanceUID, studyInsUID.c_str());// 检查信息序列DcmItem *pReferencedStudySequence = NULL;cond = pWLDataSet->findOrCreateSequenceItem(DCM_ReferencedStudySequence, pReferencedStudySequence, -2);pReferencedStudySequence->putAndInsertString(DCM_ReferencedSOPClassUID, \"\");pReferencedStudySequence->putAndInsertString(DCM_ReferencedSOPInstanceUID, \"\");pWLDataSet->putAndInsertString(DCM_RequestedProcedurePriority, \"NORMAL\");pWLDataSet->putAndInsertString(DCM_PatientTransportArrangements, \"\");// 申请医生pWLDataSet->putAndInsertString(DCM_RequestingPhysician, wlInfo.RequestPhysician.c_str());pWLDataSet->putAndInsertString(DCM_ReferringPhysicianName, \"\");// 申请科室pWLDataSet->putAndInsertString(DCM_CurrentPatientLocation, wlInfo.RequestDepartment.c_str());pWLDataSet->putAndInsertString(DCM_InstitutionalDepartmentName, wlInfo.RequestDepartment.c_str());// 床号pWLDataSet->putAndInsertString(DCM_PatientInstitutionResidence, wlInfo.BedNo.c_str());// 患者信息序列DcmItem *pReferencedPatientSequenceItem = NULL;cond = pWLDataSet->findOrCreateSequenceItem(DCM_ReferencedPatientSequence, pReferencedPatientSequenceItem, -2);pReferencedPatientSequenceItem->putAndInsertString(DCM_ReferencedSOPClassUID, \"\");pReferencedPatientSequenceItem->putAndInsertString(DCM_ReferencedSOPInstanceUID, \"\");// 患者姓名pWLDataSet->putAndInsertString(DCM_PatientName, wlInfo.PatientName.c_str());pWLDataSet->putAndInsertString(DCM_PatientID, wlInfo.StudyId.c_str());pWLDataSet->putAndInsertString(DCM_AccessionNumber, wlInfo.AccNumber.c_str());// 住院号/门诊号/体检号pWLDataSet->putAndInsertString(DCM_AdmissionID, wlInfo.MedicalNo.c_str());// 生日pWLDataSet->putAndInsertString(DCM_PatientBirthDate, wlInfo.Birthday.c_str());// 性别pWLDataSet->putAndInsertString(DCM_PatientSex, wlInfo.Sex.c_str());// 身高体重pWLDataSet->putAndInsertString(DCM_PatientWeight, wlInfo.PatWeight.c_str());pWLDataSet->putAndInsertString(DCM_PatientSize, wlInfo.PatHeight.c_str());// 年龄pWLDataSet->putAndInsertString(DCM_PatientAge, wlInfo.StudyAge.c_str());std::string sendStr = \"\";sendStr += \"姓名:\" + wlInfo.PatientName + \",\";sendStr += \"性别:\" + wlInfo.Sex + \",\";sendStr += \"年龄:\" + wlInfo.StudyAge + \",\";sendStr += \"生日:\" + wlInfo.Birthday + \",\";sendStr += \"日期:\" + wlInfo.RegistyDate + \",\";sendStr += \"检查号:\" + wlInfo.StudyId + \",\";sendStr += \"字符集:\" + strCharSet;m_pServer->log_info(\"发送到[%s]:[%s]\", m_peerAET.c_str(), sendStr.c_str());HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(pWLDataSet, DCM_ReferencedStudySequence);HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(pWLDataSet, DCM_ReferencedPatientSequence);// 最后一条记录打印debug日志if (m_matchingDatasets.size() == 1){std::ostringstream msg;msg.str(\"\");m_pServer->log_debug(\"Last Find SCP Response:\");pWLDataSet->print(msg);std::string tmp;tmp = msg.str().c_str();Replace(tmp, \"\\n\", \"\\r\\n\");m_pServer->log_debug(\"=============================\");m_pServer->log_debug(tmp.c_str());m_pServer->log_debug(\"=============================\");}m_matchingDatasets.pop_front();rStatus = WLM_PENDING;return pWLDataSet;}

2.2 WlmDataSourceJson类

WlmDataSourceJson类从XgDataSource类派生,重写ConnectToDataSource,DisconnectFromDataSource,StartFindRequest三个函数,实现从json文件中读取登记信息。
读写json文件请参考我的文章c++ nlohmann/json读写json文件
头文件:

#pragma once#include \"XgDataSource.h\"class WlmDataSourceJson : public XgDataSource{public:WlmDataSourceJson();WlmDataSourceJson(CWLServer* server);~WlmDataSourceJson();// 测试数据static void GenTestData();static int ReadData(std::list<WLINFO>& data);protected:// 必须实现的父类虚函数OFCondition ConnectToDataSource() override;OFCondition DisconnectFromDataSource() override;WlmDataSourceStatusType StartFindRequest(const DcmDataset &findRequestIdentifiers) override;private:std::list<WLINFO>m_fullDatasets;};

源文件:

#include \"pch.h\"#include \"WlmDataSourceJson.h\"#include \"Utilities.h\"#include \"CWLServer.h\"#include \"json.hpp\"using json = nlohmann::json;#pragma comment(lib, \"Iphlpapi.lib\")#pragma comment(lib, \"ws2_32.lib\")#pragma comment(lib, \"netapi32.lib\")#pragma comment(lib, \"dcmnet.lib\")#pragma comment(lib, \"dcmdata.lib\")#pragma comment(lib, \"oflog.lib\")#pragma comment(lib, \"ofstd.lib\")#pragma comment(lib, \"dcmtls.lib\")#pragma comment(lib, \"oficonv.lib\")#pragma comment(lib,\"dcmwlm.lib\")#ifdef _DEBUG#pragma comment(lib,\"zlib_d.lib\")#else#pragma comment(lib,\"zlib_o.lib\")#endifWlmDataSourceJson::WlmDataSourceJson(): XgDataSource(){}WlmDataSourceJson::WlmDataSourceJson(CWLServer* server): XgDataSource(server){}WlmDataSourceJson::~WlmDataSourceJson(){}void WlmDataSourceJson::GenTestData(){std::vector<nlohmann::json> v = {{ {\"studyId\", 1}, {\"patName\", u8\"测试json1\"}, {\"gender\", u8\"男\"}, {\"age\", \"46Y\"}, {\"regDate\", \"2025-07-18 09:01:01\"}, {\"modality\", \"CT\"}, {\"examItem\", u8\"胸部X线计算机体层(CT)平扫\"} },{ {\"studyId\", 2}, {\"patName\", \"jsonName2\"}, {\"gender\", u8\"男\"}, {\"age\", \"75Y\"}, {\"regDate\", \"2025-07-18 09:01:01\"}, {\"modality\", \"CT\"}, {\"examItem\", u8\"颅脑X线计算机体层(CT)平扫\"} },{ {\"studyId\", 3}, {\"patName\", \"jsonName3\"}, {\"gender\", u8\"女\"}, {\"age\", \"66Y\"}, {\"regDate\", \"2025-07-18 09:01:01\"}, {\"modality\", \"CT\"}, {\"examItem\", u8\"胸部X线计算机体层(CT)平扫\"} }};nlohmann::json j_data(v);std::string appDir = GetAppPath();std::string fn = appDir + \"wl.json\";std::ofstream o(fn);//o << std::setw(4) << j_data << std::endl;o << j_data.dump(4) << std::endl;}int WlmDataSourceJson::ReadData(std::list<WLINFO>& data){std::string appDir = GetAppPath();std::string fn = appDir + \"wl.json\";std::ifstream f(fn);json j_data = json::parse(f);if (!j_data.is_array()) {return 0;}for (const auto& item : j_data) {WLINFO info;int id = item[\"studyId\"];info.StudyId = info.AccNumber = info.PatientId = std::to_string(id);info.PatientName = UTF8toA(item[\"patName\"]);info.Sex = UTF8toA(item[\"gender\"]);info.StudyAge = item[\"age\"];info.RegistyDate = item[\"regDate\"];info.Modality = item[\"modality\"];info.ExamItem = UTF8toA(item[\"examItem\"]);data.push_back(info);}return data.size();}OFCondition WlmDataSourceJson::ConnectToDataSource(){try{std::ifstream f(m_source);json j_data = json::parse(f);if (!j_data.is_array()) {return makeDcmnetCondition(0, OF_error, \"解析json失败!\");}for (const auto& item : j_data) {WLINFO info;int id = item[\"studyId\"];info.StudyId = info.AccNumber = info.PatientId = std::to_string(id);info.PatientName = UTF8toA(item[\"patName\"]);info.Sex = UTF8toA(item[\"gender\"]);info.StudyAge = item[\"age\"];info.RegistyDate = item[\"regDate\"];info.Modality = item[\"modality\"];info.ExamItem = UTF8toA(item[\"examItem\"]);m_fullDatasets.push_back(info);}}catch (const std::exception&){return makeDcmnetCondition(0, OF_error, \"解析json失败!\");}return EC_Normal;}OFCondition WlmDataSourceJson::DisconnectFromDataSource(){return EC_Normal;}WlmDataSourceStatusType WlmDataSourceJson::StartFindRequest(const DcmDataset &findRequestIdentifiers){// 从数据源(文件或数据库)中查找匹配的登记信息if (ConnectToDataSource().bad()){m_pServer->log_error(\"查询工作列表, 连接数据源失败!\");return WLM_FAILED_IDENTIFIER_DOES_NOT_MATCH_SOP_CLASS;}ClearDataset(identifiers);delete identifiers;identifiers = new DcmDataset(findRequestIdentifiers);identifiers->computeGroupLengthAndPadding(EGL_withoutGL, EPD_withoutPadding);//查询条件OFCondition cond;OFString PatName;OFString PatId;OFString StartDate;OFString StartTime;OFString EndDate;OFString EndTime;OFString Sex;OFString Modality;OFString charset;identifiers->findAndGetOFString(DCM_SpecificCharacterSet, charset);identifiers->findAndGetOFString(DCM_PatientName, PatName);identifiers->findAndGetOFString(DCM_PatientID, PatId);identifiers->findAndGetOFString(DCM_PatientSex, Sex);if (Sex == \"M\") Sex = \"男\";if (Sex == \"F\") Sex = \"女\";DcmItem *pItem;identifiers->findOrCreateSequenceItem(DCM_ScheduledProcedureStepSequence, pItem, -1);DcmElement *elm = NULL;DcmSequenceOfItems *seq = NULL;if (pItem){pItem->findAndGetOFString(DCM_ScheduledProcedureStepStartDate, StartDate);pItem->findAndGetOFString(DCM_ScheduledProcedureStepStartTime, StartTime);pItem->findAndGetOFString(DCM_ScheduledProcedureStepEndDate, EndDate);pItem->findAndGetOFString(DCM_ScheduledProcedureStepEndTime, EndTime);}std::copy_if(m_fullDatasets.begin(), m_fullDatasets.end(), std::back_inserter(m_matchingDatasets),[&](WLINFO item) {bool bOk = true;if (!PatName.empty() && item.PatientName.find(PatName)==std::string::npos) {bOk = false;}if (!PatId.empty() && item.PatientId != PatId) {bOk = false;}if (!Sex.empty() && item.Sex != Sex) {bOk = false;}if (!StartDate.empty() && !EndDate.empty()) {// 简化比较, 正常要转化为日期再比较if (item.RegistyDate < StartDate || item.RegistyDate > EndDate) {bOk = false;}}return bOk;}); m_pServer->log_info(\"查询到 %d 条检查记录\", m_matchingDatasets.size());if (m_matchingDatasets.size() > 0){return WLM_PENDING;}return WLM_SUCCESS;}

2.3 WlmDataSourceSqlite类

WlmDataSourceSqlite类从XgDataSource类派生,重写ConnectToDataSource,DisconnectFromDataSource,StartFindRequest三个函数,实现从sqlitle3数据库文件中读取登记信息。
读写sqlite数据库请参考我的文章vs2017 c++ 使用sqlite3数据库

头文件:

#pragma once#include \"XgDataSource.h\"struct sqlite3;class WlmDataSourceSqlite : public XgDataSource{public:WlmDataSourceSqlite();WlmDataSourceSqlite(CWLServer* server);~WlmDataSourceSqlite();protected:// 必须实现的父类虚函数OFCondition ConnectToDataSource() override;OFCondition DisconnectFromDataSource() override;WlmDataSourceStatusType StartFindRequest(const DcmDataset &findRequestIdentifiers) override;private:sqlite3* m_db;};

源文件:

#include \"pch.h\"#include \"WlmDataSourceSqlite.h\"#include \"CWLServer.h\"#include \"sqlite3.h\"#include \"Utilities.h\"#pragma comment(lib, \"sqlite3.lib\")WlmDataSourceSqlite::WlmDataSourceSqlite(): XgDataSource(), m_db(nullptr){}WlmDataSourceSqlite::WlmDataSourceSqlite(CWLServer* server): XgDataSource(server), m_db(nullptr){}WlmDataSourceSqlite::~WlmDataSourceSqlite(){DisconnectFromDataSource();}OFCondition WlmDataSourceSqlite::ConnectToDataSource(){//std::string dbfn = GetAppPath() + \"wl.db\";std::string dbfn = m_source;int rc = sqlite3_open(dbfn.c_str(), &m_db);if (SQLITE_OK != rc) {m_pServer->log_error(\"open sqlite failed.%s:%d\", __FUNCTION__, __LINE__);return makeDcmnetCondition(0, OF_error, \"open sqlite failed\");}return EC_Normal;}OFCondition WlmDataSourceSqlite::DisconnectFromDataSource(){int rc = SQLITE_ERROR;if (m_db) {rc = sqlite3_close_v2(m_db);if (rc == SQLITE_OK) m_db = nullptr;}return EC_Normal;}WlmDataSourceStatusType WlmDataSourceSqlite::StartFindRequest(const DcmDataset &findRequestIdentifiers){// 从数据源(文件或数据库)中查找匹配的登记信息if (ConnectToDataSource().bad()){m_pServer->log_error(\"查询工作列表, 连接数据源失败!\");return WLM_FAILED_IDENTIFIER_DOES_NOT_MATCH_SOP_CLASS;}ClearDataset(identifiers);delete identifiers;identifiers = new DcmDataset(findRequestIdentifiers);identifiers->computeGroupLengthAndPadding(EGL_withoutGL, EPD_withoutPadding);//查询条件OFCondition cond;OFString PatName;OFString PatId;OFString StartDate;OFString StartTime;OFString EndDate;OFString EndTime;OFString Sex;OFString Modality;OFString charset;identifiers->findAndGetOFString(DCM_SpecificCharacterSet, charset);identifiers->findAndGetOFString(DCM_PatientName, PatName);identifiers->findAndGetOFString(DCM_PatientID, PatId);identifiers->findAndGetOFString(DCM_PatientSex, Sex);if (Sex == \"M\") Sex = \"男\";if (Sex == \"F\") Sex = \"女\";DcmItem *pItem;identifiers->findOrCreateSequenceItem(DCM_ScheduledProcedureStepSequence, pItem, -1);DcmElement *elm = NULL;DcmSequenceOfItems *seq = NULL;if (pItem){pItem->findAndGetOFString(DCM_ScheduledProcedureStepStartDate, StartDate);pItem->findAndGetOFString(DCM_ScheduledProcedureStepStartTime, StartTime);pItem->findAndGetOFString(DCM_ScheduledProcedureStepEndDate, EndDate);pItem->findAndGetOFString(DCM_ScheduledProcedureStepEndTime, EndTime);}std::string sql;sql = \"select studyId, patId, patName, gender, age, modality, IFNULL(requestPhysician, \'\'), regDate, IFNULL(examItem, \'\') from Registry where 1=1\";if (!PatName.empty()) {sql += \" and patName like \'%\" + PatName + \"%\'\";}if (!PatId.empty()) {sql += \" and patId = \'\" + PatId + \"\'\";}if (!Sex.empty()) {sql += \" and gender = \'\" + Sex + \"\'\";}if (!StartDate.empty() && !EndDate.empty()) {std::string start, end;start = StartDate.substr(0, 4) + \"-\" + StartDate.substr(4, 2) + \"-\" + StartDate.substr(6, 2);start += \" 00:00:00\";end = EndDate.substr(0, 4) + \"-\" + EndDate.substr(4, 2) + \"-\" + EndDate.substr(6, 2);end += \" 23:59:59\";sql += \" and regDate between \'\" + start + \"\' and \'\" + end + \"\'\";}sql = AtoUTF8(sql);int nCols = -1;int nRows = -1;char** pResult = NULL;char* errMsg = NULL;int index = 0;const int ret = sqlite3_get_table(m_db, sql.c_str(), &pResult, &nRows, &nCols, &errMsg);index = nCols;for (int i = 0; i < nRows; i++){WLINFO info;int rowStart = (i + 1) * nCols;info.StudyId = info.AccNumber = pResult[rowStart + 0];info.PatientId = pResult[rowStart + 1];info.PatientName = UTF8toA(pResult[rowStart + 2]);info.Sex = UTF8toA(pResult[rowStart + 3]);info.StudyAge = UTF8toA(pResult[rowStart + 4]);info.Modality = pResult[rowStart + 5];info.RequestPhysician = UTF8toA(pResult[rowStart + 6]);info.RegistyDate = pResult[rowStart + 7];info.ExamItem = UTF8toA(pResult[rowStart + 8]);m_matchingDatasets.push_back(info);}m_pServer->log_info(\"查询到 %d 条检查记录\", m_matchingDatasets.size());if (m_matchingDatasets.size() > 0){return WLM_PENDING;}return WLM_SUCCESS;}

三、工程源码

下载WorklistServer.exe
下载工程源码