服务器接收不到dicom文件,DICOM医学图像处理:DIMSE消息发送与接收“大同小异”之DCMTK fo-dicom mDCM..._赵学浩的博客-程序员ITS304

技术标签: 服务器接收不到dicom文件  

背景:

从DICOM网络传输一文开始,相继介绍了C-ECHO、C-FIND、C-STORE、C-MOVE等DIMSE-C服务的简单实现,博文中的代码给出的实例都是基于fo-dicom库来实现的,原因只有一个:基于C#的fo-dicom库具有高封装性。对于初学者来说实现大多数的DIMSE-C、DIMSE-N服务几乎都是“傻瓜式”操作——构造C-XXX-RQ、N-XXX-RQ然后绑定相应的OnResponseReceived处理函数即可。本博文希望在前几篇预热的基础上,对比DCMTK、fo-dicom、mDCM三种库构建DIMSE消息的具体操作,来分析一下三者对于DIMSE消息的发送和接收的实现,为后续搭建简易版的Dicom Server服务器做准备。

DIMSE:

DIMSE,是DICOM Message Service Element的简称。DICOM3.0第7部分指出:DIMSE为对等DICOM应用实体进行医学影像及相关信息交换提供了一种应用服务元素定义(Application Service Element),包括服务和协议(DIMSE Service 和DIMSE Protocol)。

DIMSE Protocol:

DIMSE基于DIMSE协议来提供服务,DIMSE协议规定了构造消息必需的编码规则。一条DICOM MESSAGE由固定的指令集合(Command Set),外加可选择的数据集合(Data Set)构成,如下截图所示:

37c233fc68d36cb55123d66d01080a56.png

可以简单的理解为Command Set就是本博文即将要介绍的各种服务的请求和应答消息;而Data Set可以认为类似于DCM后缀的文件,是我们希望在对等DICOM实体间进行传输的信息。但是从本质上来说Command Set和Data Set两者都遵循DICOM3.0协议中IOD的定义,都是“以(Group Number,Element Number)对”来进行标记的Data Element元素的集合,更形象一点的说明可参考早期的博文(http://blog.csdn.net/zssureqh/article/details/9275271)。

DIMSE Protocol指出Message可能会分片(fragmented,这与传统的TCP/IP中的概念类似),消息的具体传输是基于ASSOCIATE(DICOM3.0第8部分)中的P-DATA Service(博文http://blog.csdn.net/zssureqh/article/details/41016091中有介绍)。

DIMSE Services:

DIMSE服务因操作SOP类型的不同分为DIMSE-C Services和DIMSE-N Services,DIMSE-C服务支持在对等DICOM实体间进行Composite SOP Instance操作,主要包括C-ECHO、C-FIND、C-STORE、C-MOVE、C-GET等;而DIMSE-N服务支持Normalized SOP Instance操作,主要包括N-EVENT-REPORT、N-GET、N-SET、N-CREATE、N-ACTION、N-DELETE。

b5841759f3c149ff84b6498f8fd25bc1.png

从上图可以看出DIMSE-C服务只提供操作服务,即对等DICOM实体一方请求另一方对Composite SOP Instance进行操作(operation);而DIMSE-N服务除了提供操作以外,还提供通知(notification)服务。DIMSE中的所有操作和通知都是确认服务(confirmed services),即一方发出的请求都需要得到对方的应答(原文:All DIMSE operations and notifications are confirmed services. The performing DIMSE-service-user shall report the response of each operation or notificationover the same Associationon which the operation or notification was invoked.)。每种服务具体方式不同,例如某些操作可能会触发后续的子操作、某些操作可能需要多个响应等等,如下图:

87a880abc71937b046db29e5d1dfe848.png

DCMTK、fo-dicom、mDCM构建DIMSE-C消息:

DIMSE-C服务在医学领域应用最广泛,常见的PACS、HIS、RIS、LIS等系统都会用到,而DIMSE-N服务主要应用在MPPS和DICOM打印中,日常学习中可能没有实际应用和测试的机会,因此这里就暂时不介绍,主要以DIMSE-C消息的构造为主,来分别介绍三种库的具体操作。

DCMTK:

博文http://blog.csdn.net/zssureqh/article/details/41016091之前简单介绍了一下DCMTK对于网络传输方面的封装,更多的是偏重于协议的各层(Layer),例如对最底层的基于TCP/IP的Dicom Upper Layer的封装以DUL_为前缀;对实体连接层的封装以ASC_为前缀;最顶层的是DIMSE层,以DIMSE_为前缀。本博文会从DIMSE Services中的DIMSE-C各种消息入手,介绍DCMTK对于消息的封装和操作:

DCMTK之C-ECHO:

DCMTK开源库相较于其他两者来说最大的优势是有完整的说明文档、稳定的维护团队,同时也有成功的商业产品。在源码中也给出了各种服务工具包,前面的好多博文都已经介绍过DCMTK的工具包,例如针对于C-ECHO的echoscu.exe(博文后续的工程实例是用dcmqrscp.exe作为mini DICOM服务端进行测试的)。

DCMTK对DIMSE-C中的各种消息的定义在dimse.h头文件中,其中C-ECHO-RQ消息定义如下:

/* C-ECHO */

struct T_DIMSE_C_EchoRQ {

DIC_US MessageID; /* M */

DIC_UI AffectedSOPClassUID; /* M */

T_DIMSE_DataSetType DataSetType; /* M */

} ;

struct T_DIMSE_C_EchoRSP {

DIC_US MessageIDBeingRespondedTo; /* M */

DIC_UI AffectedSOPClassUID; /* U(=) */

T_DIMSE_DataSetType DataSetType; /* M */

DIC_US DimseStatus; /* M */

unsigned int opts; /* which optional items are set */

#define O_ECHO_AFFECTEDSOPCLASSUID 0x0001

} ;

dimse.h中对于每一种DIMSE-C服务的请求消息(request)和响应消息(response)都给出了定义,并以union方式来统一了DICOM Message结构,如下所示:

/*

* Composite DIMSE Message

*/

struct T_DIMSE_Message {

T_DIMSE_Command CommandField; /* M */

union {

/* requests */

T_DIMSE_C_StoreRQ CStoreRQ;

T_DIMSE_C_EchoRQ CEchoRQ;

T_DIMSE_C_FindRQ CFindRQ;

T_DIMSE_C_GetRQ CGetRQ;

T_DIMSE_C_MoveRQ CMoveRQ;

T_DIMSE_C_CancelRQ CCancelRQ;

T_DIMSE_N_EventReportRQ NEventReportRQ;

T_DIMSE_N_GetRQ NGetRQ;

T_DIMSE_N_SetRQ NSetRQ;

T_DIMSE_N_ActionRQ NActionRQ;

T_DIMSE_N_CreateRQ NCreateRQ;

T_DIMSE_N_DeleteRQ NDeleteRQ;

/* responses */

T_DIMSE_C_StoreRSP CStoreRSP;

T_DIMSE_C_EchoRSP CEchoRSP;

T_DIMSE_C_FindRSP CFindRSP;

T_DIMSE_C_GetRSP CGetRSP;

T_DIMSE_C_MoveRSP CMoveRSP;

T_DIMSE_N_EventReportRSP NEventReportRSP;

T_DIMSE_N_GetRSP NGetRSP;

T_DIMSE_N_SetRSP NSetRSP;

T_DIMSE_N_ActionRSP NActionRSP;

T_DIMSE_N_CreateRSP NCreateRSP;

T_DIMSE_N_DeleteRSP NDeleteRSP;

} msg;

};

DICOM3.0第7部分中有关于C-ECHO消息的参数说明以及具体指令编码,正如前文所述Command同样也是以(Group Number,Element Number)标记的Data Element元素的集合,因此按照DICOM3.0标准中的要求只要向C-ECHO-RQ或者C-ECHO-RSP指令中插入规定的Data Element元素即可。

61ca34534e9e4d5b255d3dd617d4f10c.png

如上图所示,构造T_DIMSE_CEchoRQ需要填充MessageID/Affected SOP Class UID等,具体构造代码如下:(代码封装在DIMSE_echoUser函数中)

T_DIMSE_Message req, rsp;

T_ASC_PresentationContextID presID;

const char *sopClass = UID_VerificationSOPClass;

bzero((char*)&req, sizeof(req));

bzero((char*)&rsp, sizeof(rsp));

req.CommandField = DIMSE_C_ECHO_RQ;

req.msg.CEchoRQ.MessageID = msgId;

strcpy(req.msg.CEchoRQ.AffectedSOPClassUID,

sopClass);

req.msg.CEchoRQ.DataSetType = DIMSE_DATASET_NULL;

上面代码中的rsp与我们自己构建的req类似,唯一不同的是req是在C-ECHO SCU端构造,而rsp是在C-ECHO SCP端构造并通过网络传送过来的。

(具体的测试代码可参见博文后文给出的连接)

DCMTK之C-FIND:

下面我们看一下比较复杂的消息C-FIND,相较于C-ECHO消息,C-FIND中需要给出我们希望查询的目标属性列表(记住:同样也是一个DcmDataset类型,即Dicom Element集合)。

b34255f201e30c5a9cc6bf713777c2fa.png

C-FIND-RQ消息的构造代码如下:

//定义临时变量

T_ASC_PresentationContextID presId;

T_DIMSE_C_FindRQ req;

T_DIMSE_C_FindRSP rsp;

DcmFileFormat dcmff;

OFString temp_str;

presId=ASC_findAcceptedPresentationContextID(assoc,abstractSyntax);

//构造C-FIND-RQ消息

bzero(OFreinterpret_cast(char*, &req), sizeof(req));

strcpy(req.AffectedSOPClassUID,abstractSyntax);

req.DataSetType=DIMSE_DATASET_PRESENT;

req.Priority=DIMSE_PRIORITY_LOW;

req.MessageID=assoc->nextMsgID++;

//构造数据体,即我们具体希望在C-FIND SCP端获得的信息

DcmDataset* dcmdataset=new DcmDataset();

dcmdataset->putAndInsertString(DCM_StudyInstanceUID,"");

dcmdataset->putAndInsertString(DCM_StudyDate,"");

dcmdataset->putAndInsertString(DCM_QueryRetrieveLevel,"STUDY");

DcmDataset *statusDetail = NULL;

//在DIMSE_findUser内部会将dcmdataset数据合并到req中,统一构成T_DIMSE_Message

OFCondition cond=DIMSE_findUser(assoc,presId,&req,dcmdataset,NULL,NULL,blockMode,dimse_timeout,&rsp,&statusDetail);

上述代码比较复杂的是需要构造参数列表中的Identifier元素,该元素包含了我们希望从C-FIND SCP服务端提供查询获得的属性,上面选择了STUDY级别的查询,因此需要添加DCM_QueryRetrieveLevel元素、StudyInstanceUID等(DCM_QueryRetrieveLevel元素必须添加,有时候会误认为添加了AffectedSOPClassUID后就不需要了,这是错误的。否则服务端会返回如下错误,如下图)。

64e6065fe688cf9d85f6895fb1bc56a9.png

注:关于Patient、Study、Series等不同级别的查询的详细介绍可参考DICOM3.0标准第4部分的附录C。

DCMTK之C-STORE:

C-STORE与C-FIND类似,同样需要添加额外的数据,不同于C-FIND添加查询属性列表的是,C-STORE添加的是准备发送的DCM文件的数据体,即下图中的Data Set。

OFCondition cond = EC_Normal;

T_DIMSE_Message req, rsp;

DcmDataset

bzero((char*)&req, sizeof(req));

bzero((char*)&rsp, sizeof(rsp));

/* set corresponding values in the request message variable */

req.CommandField = DIMSE_C_STORE_RQ;

request->DataSetType = DIMSE_DATASET_PRESENT;

request->req.msg.CStoreRQ = *request;

暂时我们就只介绍C-ECHO、C-FIND和C-STORE三种服务的请求消息构造方法,其他的类似。

fo-dicom:

fo-dicom是基于C#开发的,封装性更强,封装思路更倾向于按DICOM消息流来进行,即fo-dicom库开发者在实现了整个DIMSE消息流框架的基础上,通过给用户预留各阶段的接口来方便用户定制自己的实现。对于DIMSE消息流框架的封装在DicomService.cs文件中(同时也有类似于DCMTK中的ASC_方面的封装,主要指的是A-ASSOCIATE服务及协议,在DICOM3.0第8部分有详细介绍),对于网络底层的封装放在DicomServer.cs文件中(等同于DCMTK中的DUL_层)。

DICOM Message消息的基类在DicomMessage.cs文件中,然后根据请求和应答派生了两个基类DicomRequest和DicomResponse。从fo-dicom库的封装以及fo-dicom对于Dataset的设计可以看出Command和Dataset都是数据集合,不同的是两者存储的元素类型不同。

在fo-dicom库中构造各类消息很方便,可谓是“傻瓜式”操作,详情如下:

fo-dicom之C-ECHO:

DicomCEchoRequest cechoRQ=new DicomCEchoRequest();

一行代码就顺利的构建了一个C-ECHO-RQ请求指令。分析源码可知DicomCEchoRequest继承自DicomRequest,DicomReqeust继承自DicomMessage。逐级查看各类的构造函数可以发现。虽然我们调用的是DicomCEchoRequest的默认构造函数,但是在相继调用了基类DicomRequest(DicomCommandField.CEchoRequest, DicomUID.Verification, priority)和DicomMessage()后,顺利的完成了对C-ECHO-RQ指令中各个参数构造,其中DicomMessage中构造了空的Command Set和DataSet,DicomRequest中对MessageID、Priority、SOPClassUID以及CommandFieldType进行了赋值,这简直是太容易啦,不过也正因为此,刚入手的时候可能不知道如何来定制化自己的请求,以为fo-dicom库留给我们的可操作性太少,其实不然,继续往下看。

fo-dicom之C-FIND:

DicomCFindRequest cfind=DicomCFindRequest.CreateStudyQuery(patientId:”12345”);

cfind.OnResponseReceived=(rq,rsp)=>

{

//接收到C-FIND-RSP响应消息后,本机C-FIND SCU进行的操作

//例如可以输出到屏幕或其他窗口

Console.WriteLine("PatientAge:{0} PatientName:{1}", rsp.Dataset.Get(DicomTag.PatientAge), rsp.Dataset.Get(DicomTag.PatientName));

}

通过对比fo-dicom与DCMTK中C-FIND的构造,是不是觉得很容易。但是越容易学习和上手的东西,倘若不掌握其本质越容易忘。查看DicomCFindRequest.cs源码,可以发现CreateStudyQuery函数已经帮助我们添加了Study查询级别所需的所有字段,也就是上文中提到的Identifier参数部分。代码如下:

那么如果我们想像DCMTK那样自由添加字段怎么办?例如在已知服务端是自己定制实现的基础上来查询我们的私有字段。很简单直接覆盖一下CreateStudyQuery函数即可。另外fo-dicom还有一个比价便利的地方是将每种消息的回调函数直接绑定到消息中,程序写起来比较方便,逻辑上更清晰。

fo-dicom之C-STORE:

DicomCStoreRequest cstore=new DicomCStoreRequest(@”c:\\test4.dcm”);

在DicomCStoreRequest一级只需要数据要发送的dcm文件名(全路径名),同样通过逐级来完成CommandSet和Dataset的赋值。基本流程如下:

///

/// Initializes DICOM C-Store request to be sent to SCP.

///

/// DICOM file to be sent

/// Priority of request

public DicomCStoreRequest(DicomFile file, DicomPriority priority = DicomPriority.Medium) : base(DicomCommandField.CStoreRequest, file.Dataset.Get(DicomTag.SOPClassUID), priority) {

File = file;

Dataset = file.Dataset;

SOPInstanceUID = File.Dataset.Get(DicomTag.SOPInstanceUID);

}

//DicomRequest.cs文件

protected DicomRequest(DicomCommandField type, DicomUID affectedClassUid, DicomPriority priority) : base() {

Type = type;

SOPClassUID = affectedClassUid;

MessageID = GetNextMessageID();

Priority = priority;

Dataset = null;

}

//DicomMessage.cs文件

public DicomMessage() {

Command = new DicomDataset();

Dataset = null;

}

mDCM:

mDCM库与fo-dicom库其实是相同的,只不过fo-dicom利用了最新的C#技术来重构mDCM。如博文http://blog.csdn.net/zssureqh/article/details/39621533中给出的mDCM库的继承图所示,在顶层基类DcmNetworkBase中实现了DIMSE消息流的基本框架,然后按照Client和Server进行了两路派生。mDCM的封装有点处于DCMTK和fo-dicom之间的状况,既未做到像DCMTK那样完全提供各个层面底层操作函数,也没有像fo-dicom那样更抽象的封装。

c0869ee1ff4f4f597854afc22412efdd.png

下面来看一下mDCM对各种消息的构造:

mDCM之C-ECHO:

//DcmAssociation assoction;//已经顺利建立的DICOM对等实体间的连接

byte pcid=associate.FindAbstractSyntax(DicomUID.VerificationSOPClass;

SenCEchoRequest(pcid,NextMessageID(),Priority);

mDCM比较特殊,对于DIMSE-C服务请求的参数赋值流程与fo-dicom类似,大多参数赋值都在基类中完成,例如DcmClientBase中完成了MaxPDU、Priority,DicomClient完成CallingAE和CalledAE等;而对于整体请求消息的拼接却又类似DCMTK,在SendCEchoRequest函数内部调用CreateRequest来完成。

mDCM之C-FIND:

byte pcid = Associate.FindAbstractSyntax(FindSopClassUID);

if (Associate.GetPresentationContextResult(pcid) == DcmPresContextResult.Accept) {

DcmDataset dataset = query.ToDataset(Associate.GetAcceptedTransferSyntax(pcid));

SendCFindRequest(pcid, NextMessageID(), Priority, dataset);

在query.ToDataset函数内部完成了查询级别QueryRetrieveLevel的赋值,另外需要注意的是此时在ToDataset函数内部调用了一个虚函数AdditonalMembers用于方便派生添加自已要查询的Identifier元素。最终还是在SendCFindRequest函数内部利用CreateRequest创建C-FIND-RQ消息(在mDCM中的类型是DcmCommand)。

mDCM之C-STORE:

internal void SendCStoreRequest(byte pcid, DicomUID instUid, Stream stream) {

SendCStoreRequest(pcid, NextMessageID(), instUid, Priority, stream);

}

internal void SendCStoreRequest(byte pcid, DicomUID instUid, DcmDataset dataset) {

SendCStoreRequest(pcid, NextMessageID(), instUid, Priority, dataset);

}

在CStoreClient类内部通过Load来载入dcm文件,提取DcmDataset数据体,然后调用SendCStoreRequest来发送C-STORE-RQ请求(DicomCStoreClient中有两种类型的SendCStoreRequest,一种是发送DcmDataset类型数据,一种是发送Stream类型数据)。

总结:

通过对比分析三种开源库对DIMSE-C服务消息的构造方式,可以更清晰的了解DCMTK、fo-dicom、mDCM三者各自的优势。如果想了解DICOM协议的细节及底部代码的具体实现,自然DCMTK是首选,其按照Dicom Upper Layer、A-ASSOCIATE、DIMSE三层来划分的结构更方便我们研究DICOM网络传输的机制。并且DCMTK最新的3.6.1版本也逐渐开始按服务来对DUL_、ASC_、DIMSE_三类函数进行封装,已经实现了C-ECHO、C-STORE服务,即DcmSCU/DcmSCP和DcmStorageSCU/DcmStorageSCP。如果想快速入手,实现DICOM的相关服务,fo-dicom自然是首选,想必这对于C#程序员来说轻而易举(mDCM可以看做是DCMTK与fo-dicom的中间地带)。

DCMTK工程实例:

后续博文介绍:

fo-dicom搭建简单的DICOM Server

时间:2014-12-06

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_35720618/article/details/119689117

智能推荐

JavaScript学习手册八:JS函数_●圆圈○的博客-程序员ITS304

第1关:用函数语句定义函数定义一个名字为mainJs()的函数;该函数有两个参数,均为字符串类型;函数的功能是返回这两个参数的拼接结果;具体请参见后续测试样例。//请在此处编写代码/********** Begin **********/function mainJs(a,b) { return a+b;}/********** End **********/第2关:用表达式定义函数定义一个匿名函数,将它赋值给变量myFunc;该函数实现求一个三位数的各个位上的数字之和

DCMTK开发笔记(一):我的第一个DCMTK demo_CaLMdoWN_的博客-程序员ITS304_dcmtk

实验平台Visual Studio 2010已编译的 DCMTK 3.6.2 Debug x64版本实验步骤在VS2010中新建Visual C++ Win32 控制台应用程序 空项目,命名为DcmtkDemo在源文件中添加新建项 .cpp文件,命名为main.cpp粘贴如下测试代码#include <stdio.h>#include <tchar.h>...

如何使用pycharm在工程中新建venv环境--(venv 三)_绛洞花主敏明的博客-程序员ITS304_pycharm venv

问题:使用pycharm打开从git上下载的项目后,会发现项目实际上中并不存在项目需要的环境,此时,就需要根据项目中的requirement.txt文件新建环境。实现步骤一、首先使用pycharm打开项目,发现项目中不存在venv环境。二、file --> setting --> project --> project interpreter --> 新建环境...

unity 监听build前、build完成后事件_凡情的博客-程序员ITS304

using System.Collections;using System.Collections.Generic;using System.IO;using UnityEditor;using UnityEditor.Build;using UnityEditor.Callbacks;using UnityEngine;// 实现接口的方式public class BuildReport:IPostprocessBuildWithReport,IPreprocessBuildWithRe

Webapi之文件上传_Song_Lynn的博客-程序员ITS304_webapi 文件上传

Webapi之文件上传范例说明:前端:vue.js + element-ui + axios后端:c# webapi先上传存储起来,然后再读取文件仅尝试过在本地调试,未验证服务器前端部分使用element-ui的上传组件// html 直接调用api<el-upload class="upload-demo" ref...

java中Scanner类nextLine()和next()的使用方法和注意事项_羽涵w的博客-程序员ITS304_scanner.nextline

Scanner实现字符串的输入有两种方法,一种是next(),一种nextLine()。next():一定要读取到有效字符后才可以结束输入,对输入有效字符之前遇到的空格键、Tab键或Enter键等结束符,next()方法会自动将其去掉,只有在输入有效字符之后,next()方法才将其后输入的空格键、Tab键或Enter键等视为分隔符或结束符。简单地说:next()查找并返回来自此扫描器的下一个完整标记。完整标记的前后是与分隔模式匹配的输入信息,所以next方法不能得到带空格的字符串。nextLine(

随便推点

ubuntu16.04安装搜狗拼音打办法以及相关问题处理_Say丶no的博客-程序员ITS304_ubuntu16.04 搜狗输入法

1.  环境介绍   Ubuntu 16.04,   sogou输入法: sogoupinyin_2.0.0.0108_amd64(搜狗拼音官网下载安装包)2.  问题表现  输入sudo dpkg -i sogoupinyin_2.2.0.0108_amd64.deb   错误信息如下:正在选中未选择的软件包 sogoupinyin。(正在读取数据库 ... 系统当前...

ASP.NET 5,.NET Core和 ASP.NET Core之间的区别_weixin_42098295的博客-程序员ITS304_.net core和.net5区别

有时我们可能对ASP.NET 5、.NET Core和 ASP.NET Core这些术语之间感觉困惑,本文就来介绍他们之间的区别。原文地址:ASP.NET 5,.NET Core和 ASP.NET Core之间的区别

Ribbon踩坑后看源码_sun3stone的博客-程序员ITS304

自定义了一个负载均衡策略,发现一个问题,服务a先调用服务b再调用服务c后,之后再调b服务404,简单的debug后发现再调b服务的时候loadbalancer里面的IRule已经变成了服务c的IRule,感觉是c服务初始化的时候把b服务的IRule覆盖了,百思不得其解。于是跟踪源码,发现了两个之前想不明白的问题,一个是LoadBalancer有IRule属性,IRule里又有LoadBalancer属性,负载均衡策略选择服务是使用的IRule里的LoadBalancer,二是每个被调用服务第一次被调用的时候

数组动态分配与静态分配的区别_程序员编程指南的博客-程序员ITS304_动态分配和静态分配的区别

所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。例如我们定义一个float型数组:float score[100]; 但是,在使用数组的时候,总有一个问题困扰着我们:数组应该有多大?在很多的情况下,你并不能确定要使用多大的数组,比...

新闻网站项目首页列表分类展示新闻_weixin_34233679的博客-程序员ITS304

1. 获取参数cid(分类id)/page(页数)/per_page(当前页上所展示数据)2.将接受到的参数转为int型3.根据分类id查询数据库4.按照新闻分类进行过滤,按照新闻发布时间倒序排序,分页展示,每页10条数据5使用分页对象获取分页后的数据6.定义容器, 遍历分页后的新闻对象,转成字典7.返回结果(响应的状态码,以及新闻数据)转载于:https://www.c...

20篇参考文献_weixin_30254435的博客-程序员ITS304

1.基于JSP的中小型酒店管理系统的设计与实现2.基于Web的酒店管理系统的设计与实现3.基于Java的酒店管理系统设计4.基于JSP的酒店信息管理系统的设计与实现5.宾馆管理系统设计与实现6.基于Java的小型宾馆管理系统设计与实现7.中小型宾馆客房管理系统的设计与实现8.基于UML的宾馆管理系统的设计与实现9.酒店管理系统的设计与实现10.酒店管理系统的设计与实现11.基于...

推荐文章

热门文章

相关标签