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

发布于:2025-07-31 ⋅ 阅读:(18) ⋅ 点赞:(0)


前言

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
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

头文件:

#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 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文件
头文件:

#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")
#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数据库

头文件:

#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
下载工程源码


网站公告

今日签到

点亮在社区的每一天
去签到