文章目录
- 前言
- 一、CWLServer类介绍
-
- [1.1 dcmtk中worklist scp的程序流程](#1.1 dcmtk中worklist scp的程序流程)
- [1.2 日志函数](#1.2 日志函数)
- [1.3. 服务初始化、启动、停止、状态查询、设置日志级别](#1.3. 服务初始化、启动、停止、状态查询、设置日志级别)
- [1.4 设置数据源](#1.4 设置数据源)
- [5. 主线程与工作线程](#5. 主线程与工作线程)
- 二、数据源类
-
- [2.1 XgDataSource类](#2.1 XgDataSource类)
- [2.2 WlmDataSourceJson类](#2.2 WlmDataSourceJson类)
- [2.3 WlmDataSourceSqlite类](#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的流程对比。
- ASC_initializeNetwork初始化网络,整理放入到主线程函数DoNetWorks
- acceptAssociation等待连接,整理放入到主线程函数DoNetWorks
- 在acceptAssociation中调用ASC_receiveAssociation接受连接,接受到连接后,新建任务放入到线程池由工作线程处理
- 服务停止DoNetWorks中调用ASC_dropNetwork关闭网络
- 工作线程函数DcmWorkThread->DealAssociation
重点
函数DealAssociation,- 调用ASC_acceptContextsWithPreferredTransferSyntaxes,
设置接受Verification SOP Class,允许echoscu;
设置接受抽象语法UID_FINDModalityWorklistInformationModel,允许接受CFind-RQ;
设置支持的传输语法 transfer Syntaxes,本项目接受所有支持的语法"we accept all supported transfer syntaxes"; - 调用acceptUnknownContextsWithPreferredTransferSyntaxes,设置接受其他未知的Storage SOP Class
- 调用ASC_getApplicationContextName获取协商结果
- 调用ASC_acknowledgeAssociation通知连接成功
- 调用processCommands处理命令,支持C-ECHO-RQ 和 DIMSE_C_FIND_RQ两种命令。
- processCommands中调用DIMSE_receiveCommand接受命令,根据命令类型分别调用echoSCP和HandleFindSCP处理。
重点
HandleFindSCP中调用DIMSE_findProvider,传入从WlmDataSource派生的类的实例做参数,本章从json中读取检查信息,类名为WlmDataSourceJson,后面详细介绍该类。- 图像接受完成调用ASC_dropSCPAssociation,ASC_destroyAssociation释放连接
- 调用ASC_acceptContextsWithPreferredTransferSyntaxes,
1.2 日志函数
参考 第四章 图像接受StoreSCP(2)中CStoreServer类
1.3. 服务初始化、启动、停止、状态查询、设置日志级别
参考 第四章 图像接受StoreSCP(2)中CStoreServer类
1.4 设置数据源
数据源支持json文件和sqlite3数据库文件,在HandleFindSCP函数中根据文件后缀名决定实例化哪个数据源类来处理数据源。
cpp
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派生类来从数据源中加载登记信息。只需要重写以下五个函数,函数作用看注释。
cpp
OFCondition ConnectToDataSource(); // 连接数据源
OFCondition DisconnectFromDataSource(); // 关闭数据库
OFBool IsCalledApplicationEntityTitleSupported(); // 白名单判断客户端是否允许查询
WlmDataSourceStatusType StartFindRequest(const DcmDataset &findRequestIdentifiers); // 查询数据
DcmDataset *NextFindResponse(WlmDataSourceStatusType &rStatus); // 发送数据
因为要实现从json文件和sqlite3数据库中读取登记信息,且IsCalledApplicationEntityTitleSupported,NextFindResponse两个函数可能共用,所以把能共用的代码提取公共类XgDataSource,这个类从WlmDataSource派生。XgDataSource中重写IsCalledApplicationEntityTitleSupported,NextFindResponse两个函数。
- 从XgDataSource中派生WlmDataSourceJson类,重写ConnectToDataSource,DisconnectFromDataSource,StartFindRequest三个函数,实现从json文件中读取登记信息。
- 从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
...
头文件:
cpp
#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;
};
源文件:
cpp
#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 item
if (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();
//else
wlInfo = 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 UID
std::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文件
头文件:
cpp
#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;
};
源文件:
cpp
#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")
#endif
WlmDataSourceJson::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数据库
头文件:
cpp
#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;
};
源文件:
cpp
#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;
}