wenmo8 发布的文章

在我们开发某个系统的时候,客户总会提出一些特定的报表需求,固定的报表格式符合他们的业务处理需要,也贴合他们的工作场景,因此我们尽可能做出符合他们实际需要的报表,这样我们的系统会得到更好的认同感。本篇随笔介绍如何基于FastReport报表工具,生成报表PDF文档展示医院处方笺的内容。

之前在随笔《
在Winform开发中使用FastReport创建报表
》介绍过FastReport这个强大的报表工具,虽然介绍了各种报表的处理代码,不过主要的案例还是官方的案例,本篇随笔介绍基于某个医院的处方笺的格式报表的处理。

FastReport.Net是一款适用于Windows Forms, ASP.NET和MVC框架的功能齐全的报表分析解决方案。FastReport.Net以C#语言编写而成并只包含可托管的代码。它与.NET Framework 2.0以及更高版本兼容。支持在报表中添加文本、图像、线条、形状、语句、条形码、矩阵、表格、RTF、选择框等,列表报表、分组报表、主从报表、多列报表,子报表都可以实现处理。通可以为终端用户提供一个报表设计器,让用户可以方便的修改现有报表和创建自定义报表。

1、定义报表模板

和其他常规的报表工具一样,FastReport.Net报表工具也需要定义好报表模板文件,然后再基于这个报表模板进行内容的呈现,报表模板一般定义标题、报表页眉、明细内容、页脚等信息。

我们来看看大概的需求效果,这个是处方笺的常规格式。

我大概需要弄个类似格式的处方笺的报表,其中处方药需要动态生成,以及患者信息、医生审核签字的地方需要动态生成,当然,二维码,条码等内容也需要一并根据信息动态生成出来,由于我主要想通过PDF展示,因此使用报表工具生成PDF文档,已经预览或者下载即可。

我们先来看看最终设计好的报表模板,在FastReport设计器里面的效果如下所示。

其中,标题部分,主要在页眉,需要展示处方列表的在数据区展示,页脚放置一些联系信息等,这样就构建了一个完整的报表模板。

创建一个报表模板,我们先要定义报表页面格式,报表报表的宽度,高度是自定义的还是标准的,还要设置它的页边距等信息,如下所示。

页边距设置如下所示。

由于这个报表包含了主表信息,和明细表的信息,我们主表动态信息,可以通过参数的绑定方式绑定,明细表则通过绑定DataTable的方式动态处理即可。

采用参数绑定,我们需要在报表设计器里面定义好我们需要的参数,如下所示。

我们一般预先定义好相关的参数,然后绑定在模板里面,并设置好内容的对其格式即可。

如报表页面里面,我们放置了一个表格,定义好表格的行列和宽度后,双击表格单元格,就可以设置表格单元格的文本内容为对应的参数了,如下界面所示。

为了展示每项的序号,我们也需要使用到系统变量,如我们需要展示下面的内容。

那么需要定义好每项的序号,和数据字段名称。

对于动态展示的明细列表部分,我们需要定义一个数据源的方式,从而可以让报表模板绑定对应的字段名称。

我根据数据表的信息,生成一个用于绑定明细列表的数据源,如下所示。

这样我们在代码绑定的时候,只需要指定Detail的名称和对应的字段名称即可,有了这些定义,我们可以在报表设计中使用字段绑定了。

在数据区拖入对应的字段定义,并调整文本大小和对其,就可以设计出明细的部分字段绑定了。

对于二维码和条码,我们可以从报表工具栏里面拖入对应的控件,并设置对应的绑定参数和显示内容即可(这些也可以通过参数,运行的时候进行动态绑定)。

最后设计好的报表如开始介绍那样,是一个完整的报表模板了。

预览的时候,我们可以看到内容绑定的地方都是空白,因为我们没有绑定数据源的原因,不过整个报表的格式已经出来了,大概就是我们需要的结果。

2、生成报表PDF内容

通过上面报表模板的设计,我们基本的前期工作就准备好了,需要的就是根据实际业务的需要,动态呈现数据了。

在绑定数据并生成PDF格式报表的时候,我们需要先构建一个报表对象,如下代码所示。

//生成PDF报表文档到具体文件
Report report = newReport();
report.Load(reportPath);

由于数据我们是动态构建的,因此我们需要准备参数数据源和字段数据源两个部分,参数我们用字典来承载,字段数据,我们用DataTable来承载,如下所示。

//定义参数和数据格式
var dict = new Dictionary<string, object>();var dt = DataTableHelper.CreateTable("ProductName,Quantity|int,Unit,Specification,HowTo,Frequency");

然后我们根据系统需要填入动态的数据,如下代码所示。

//准备数据
dict.Add("Name", info.PatientName);
dict.Add(
"Gender", info.Gender);var age =info.BirthDate.GetAge(); dict.Add("Age", age);
dict.Add(
"Telephone", info.Telephone);
dict.Add(
"CreateTime", info.CreateTime);var checkDoctor = BLLFactory<User>.Instance.GetFullNameByOpenID(info.CheckDoctor);
dict.Add(
"CheckDoctor", !string.IsNullOrEmpty(checkDoctor) ? checkDoctor : "未知");var CheckPharmacist = BLLFactory<User>.Instance.GetFullNameByOpenID(info.CheckPharmacist);
dict.Add(
"CheckPharmacist", !string.IsNullOrEmpty(CheckPharmacist) ? CheckPharmacist : "未知");var SendUser = BLLFactory<User>.Instance.GetFullNameByOpenID(info.SendUser);
dict.Add(
"SendUser", !string.IsNullOrEmpty(SendUser) ? SendUser : "未知");var qrcode = string.Format("{0}/h5/PrescriptionDetail?id={1}", ConfigData.WebsiteDomain, info.ID);
dict.Add(
"QrCode", qrcode);
dict.Add(
"BarCode", info.PrescriptionNo);if(detailList != null)
{
foreach(var item indetailList)
{
var dr =dt.NewRow();
dr[
"ProductName"] =item.ProductName;
dr[
"Quantity"] =item.Quantity;
dr[
"Unit"] =item.Unit;
dr[
"Specification"] = "";
dr[
"HowTo"] =item.HowTo;
dr[
"Frequency"] =item.Frequency;
dt.Rows.Add(dr);
}
}

最后根据上面的数据,绑定并生成PDF报表即可,如下代码所示。

//刷新数据源
report.RegisterData(dt, "Detail");foreach (string key indict.Keys)
{
report.SetParameterValue(key, dict[key]);
}
//运行报表 report.Prepare();//导出PDF报表 PDFExport export = newPDFExport();
report.Export(export, realPath);
report.Dispose();

由于这个功能我们是在微信公众号里面集成的一个报表呈现,因此我们可以通过PDF预览的方式,或者直接打开PDF文档。、

如果采用PDF在线预览方式,可以参考我随笔《
实现在线预览PDF的几种解决方案
》介绍的那样,最终采用PDFJS的在线预览方案,不管在微信端,还是Web端都是比较不错的效果。

如果采用PDFJS预览方式,那么JS代码如下所示。

    var baseUrl = "@ViewBag.WebsiteDomain/Content/JQueryTools/pdfjs/web/viewer.html";var url = baseUrl + "?file=" + filePath;//实际地址
    location.href = url;

如果是直接打开PDF,我们我们就直接传递给浏览器一个PDF文件路径即可

location.href = filePath

在微信端预览的效果如下所示。

使用FastReport报表,总体来说,工作量主要是在设计报表模板这里,通过代码实现数据绑定的工作反而非常简单,只需要指定对应的参数和字段数据表即可,而报表的设计是一项精细的工作,我们需要根据实际情况,反复调整格式和呈现的效果才能做到尽善尽美,不过整体来说FastReport提供了非常强大的报表设计和处理过程,使得我们可以在设计一些复杂报表的时候,可以更加高效。

3、采用其他报表设计-锐浪报表设计展现

在选项使用FastReport报表呈现的时候,我也试过锐浪报表的处理方式,锐浪报表的整体呈现效果也是非常不错的,这里顺便介绍一下锐浪报表的设计、运行时绑定数据源等的步骤代码,以供参考。

首先我们需要定义好一个报表的模板信息,和FastReport报表模板一样,也是类似的定义方式,报表模板设计如下所示。

上面我们可以看到,它也是有参数绑定和字段绑定两种方式。

实现数据绑定的代码如下所示。

//生成PDF报表文档到具体文件
GridExportHelper helper = newGridExportHelper(reportPath);var json = FileUtil.FileToString(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Report/Pres.json"), Encoding.UTF8);bool success =helper.ExportPdf(json, realPath, HttpContext);if(success)
{
result
= Content(exportPdfPath);//返回Web相对路径 }
helper.Dispose();
//销毁对象

其中ExportPdf接收一个JSON字符串,实现代码如下所示。

        /// <summary>
        ///导出PDF/// </summary>
        /// <typeparam name="T">列表对象类型</typeparam>
        /// <param name="list">列表对象</param>
        /// <param name="filePath">存储路径</param>
        /// <param name="context"></param>
        /// <returns></returns>
        public bool ExportPdf(string json, stringfilePath, HttpContextBase context)
{
//从对应文件中载入报表模板数据 Report.LoadFromFile(this.ReportPath);//加载JSON对象 Report.LoadDataFromXML(json);

IGRExportOption ExportOption
=Report.PrepareExport(GRExportType.gretPDF);var exportPdf =Report.ExportToBinaryObject();
Report.UnprepareExport();
var succeeded =exportPdf.SaveToFile(filePath);returnsucceeded;
}

最后呈现的大概效果如下所示。

在做微信公众号或者企业微信开发业务应用的时候,我们常常会涉及到图片预览、上传等的处理,往往业务需求不止一张图片,因此相对来说,需要考虑的全面一些,用户还需要对图片进行预览和相应的处理,在开始的时候我使用JSSDK方式,使用微信的SDK接口进行图片的上传、预览操作,后来发现通过URL.createObjectURL选定本地图片预览、上传也是非常方便的,本篇随笔针对同一个多图片的业务需求,使用JSSDK和URL.createObjectURL两种方式进行图片预览、上传、删除等常规的处理。

1、使用JSSDK对图片的处理

在一个公众号页面-问诊界面里面,我们需要让用户上传相关的图片,包括症状图片、处方图片等,每个列表可以上传多张图片,如下界面所示。

这里使用了SDK进行图片的上传处理,参考Weui的上传样式,选择本地几张图片,可以看到缩略图展示在图框里面,但是图片还没有上传,我们在保存问诊信息的时候,才启动图片文件的上传处理。

如果图片是在编辑界面中,我们需要考虑对现有图片进行删除的处理,删除前确认即可。

单击删除图标的按钮,提示用户进行图片删除确认即可。

以上就是我们几个图片处理的场景,我们来看看如何实现的。

我们以症状图片为例,它的界面HTML部分的代码如下所示。

<divclass="weui-cells__title">症状图片</div>
<divclass="weui-cells weui-cells_form">
    <divclass="weui-cell">
        <divclass="weui-cell__bd">
            <divclass="weui-uploader">
                <!--编辑的时候,放置已有图片进行预览-->
                <ulclass="weui-weui-uploader__files"style="list-style-type: none"id="imgSickPreview"></ul>
                <divclass="weui-uploader__bd">
                    <!--放置选择的图片进行预览-->
                    <ulclass="weui-weui-uploader__files"style="list-style-type: none"id="imgSick"></ul>
                    <divclass="weui-uploader__input-box">
                        <!--图片上传的图标处理-->
                        <spanid="uploaderSick"class="weui-uploader__input"></span>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

为了使用微信JSSDK来实现上传、预览图片的功能,我们需要定义好对应的JS接口,如下代码所示。

    <scriptlanguage="javascript">
        varappid= '@ViewBag.appid';varnoncestr= '@ViewBag.noncestr';varsignature= '@ViewBag.signature';vartimestamp= '@ViewBag.timestamp';

wx.config({
debug:
false,
appId: appid,
//必填,公众号的唯一标识 timestamp: timestamp,//必填,生成签名的时间戳 nonceStr: noncestr,//必填,生成签名的随机串 signature: signature,//必填,签名,见附录1 jsApiList: ['checkJsApi','chooseImage','previewImage','uploadImage','downloadImage','getLocalImgData']
});

......
</script>

在上传图片之前,我们需要通过JSSDK的方式选择图片,这里用到了chooseImage的接口,大概所需的代码如下所示。

        //上传图片集合[用微信上传的时候,记录微信mediaId集合]
        var images ={
localSickId: [],
//病情 localPresId: [],//处方 serverSickId: [],
serverPresId: []
};
//图片选择 $("#uploaderSick").click(function() {
chooseImage(
"imgSick", "sick");
});
$(
"#uploaderPres").click(function() {
chooseImage(
"imgPres", "pres");
});
//选择图片显示 functionchooseImage(ctrlName, type) {//清空集合 if (type == "sick") {
images.localSickId
=[];
}
else{
images.localPresId
=[];
}

wx.chooseImage({
count:
3, //默认9 sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有 sourceType: ['album', 'camera'], //可以指定来源是相册还是相机,默认二者都有 success: function(res) {var ctrl = $("#" +ctrlName);
ctrl.html(
"");//清空图片显示 //localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片 if (type == "sick") {
images.localSickId
=res.localIds;
}
else{
images.localPresId
=res.localIds;
}
//动态增加img标识 $.each(res.localIds, function(index, item) {
ctrl.append(
"<img class='weui-uploader__file' src='" + item + "' />");
});
}
});
}

选择图片后,就是将图片的缩略图动态的增加在指定图片框里面。然后在保存数据的时候,使用JSSDK提交图片到微信服务器,我们服务器后台再从微信服务器获取图片(通过媒体id)。

这里我们定义了两类的图片,方便区分,分别是症状图片和处方图片,因此需要定义两个类别的变量,分别存储本地和服务器返回的id集合。

我们在进行表单提交的时候,需要确认一些必填项,然后在检查是否有文件需要上传,如果有则执行上传处理后提交表单,大概的处理代码如下所示。

        //上传数据
        $("#btnOK").click(function() {var PatientName = $("#PatientName").val();if (PatientName == '' || PatientName ==undefined) {
$.toast(
'患者姓名不能为空', "forbidden");return;
}
var ProblemDetail = $("#ProblemDetail").val();if (ProblemDetail == '' || ProblemDetail ==undefined) {
$.toast(
'详细描述不能为空', "forbidden");return;
}
//上传图片 if (images.localSickId.length > 0 || images.localPresId.length > 0) {
uploadImage(submitCallback);
//通过就提交数据 } else{
submitCallback();
}
});

这里主要的图片上传处理,就是 uploadImage 函数的处理了,而submitCallback这是定义一个函数上传表单数据的。

由于微信JSSDK上传图片,是一个个上传的,我们需要把它们串联起来,一并上传。uploadImage 里面定义了一个内部函数,依次进行图片的上传。

我们通过序号来标识两类图片,图片上传成功后,我们把图片媒体的id(JSSDK返回的)记录下来,统一提交给对应数据库记录,在后台进行图片文件的提取即可。

        //上传图片
        functionuploadImage(callback) {var localIds = images.localSickId.concat(images.localPresId);//合并数组
            var i = 0, length =localIds.length;//$.toast(length);
images.serverSickId=[];
images.serverPresId
=[];//定义一个子级函数,方便递归调用 functionupload(callback) {
wx.uploadImage({
localId: localIds[i],
success:
function(res) {
i
++;//成功后加入不同的集合 if (i <=images.localSickId.length) {
images.serverSickId.push(res.serverId);
//第一部分 } else{
images.serverPresId.push(res.serverId);
//第二部分 }if (i <length) {
upload(callback);
}
else if (callback !=undefined) {
callback();
//回调函数 }
},
fail:
function(res) {
alert(JSON.stringify(res));
}
});
};

upload(callback);
}

其中我们的定义的callback函数,是用来最后上传完成后,执行表单的记录存储的,表单包含各种输入和图片的ID信息,如下是详细的表单保存操作代码。

        //在上传图片后触发的回调函数
        functionsubmitCallback() {var druglist = [];//构造集合对象
            for (var key initemDict) {//Drug_ID,DrugName,How,Freq
druglist.push({'Drug_ID': key, "DrugName": itemDict[key], 'How': howDict[key],'Freq': freqDict[key], 'Quantity': quantityDict[key]
});
}
var url = "/H5/DrugInquirySave?openid=@ViewBag.openid";var postData ={
PatientName: $(
"#PatientName").val(),
Gender: $(
"#Gender").val(),
BirthDate: $(
"#BirthDate").val(),
Telephone: $(
"#Telephone").val(),
ProblemDetail: $(
"#ProblemDetail").val(),
Creator: $(
"#Creator").val(),
ProblemItems: $(
"input[name='ProblemItems']:checked").val(),
@
if (ViewBag.Info != null) {<text>ID:'@ViewBag.Info.ID',</text> }

SickAttachGUID: $(
"#SickAttachGUID").val(),
PresAttachGUID: $(
"#PresAttachGUID").val(),
ServerSickId: JSON.stringify(images.serverSickId),
ServerPresId: JSON.stringify(images.serverPresId),
DrugList: JSON.stringify(druglist)
};

$.post(url, postData,
function(json) {//转义JSON为对象 var data =$.parseJSON(json);if(data.Success) {
$.toast(
"处方已提交审核中,稍后请到处方查询查看。");//WeixinJSBridge.call('closeWindow');//关闭窗口 location.href = "/h5/Prescription";//跳转到处方页面 }else{
$.toast(
"保存失败:" + data.ErrorMessage, "forbidden");
}
})
;
};

我们注意到,我们服务端返回的ID集合,我们分别放在了两个字段里面提交到后台处理。

ServerSickId: JSON.stringify(images.serverSickId),
ServerPresId: JSON.stringify(images.serverPresId),

在后台,我们首先需要提取用户提交的基础表单数据,如下是后台定义的函数处理

这些是常规的表单信息,我们提交到微信服务器的图片信息也需要提取出来的,这些图片分两类,每类都包含多个字符串组成的图片ID集合。

后台主要就是根据这些ID,使用微信基础接口,获取临时图片的接口方式,把图片从服务器上下载下来存储到本地服务器上。

其中UploadFile函数就是封装了如何实现图片获取、图片存储的处理逻辑,主要的代码部分逻辑如下所示。

这种方式很好的利用了JSSDK的图片选择、上传的处理,实现了我们所需要的图片预览、选择、上传等一系列操作,也能够满足实际的功能需要。

不过总感觉把图片绕了一圈再回来不太好而已。

2、使用URL.createObjectURL对图片的处理

前面介绍了使用微信JSSDK方式实现图片预览、选择、上传等一系列操作,在上传文件的时候,感觉绕了一圈再回来,一直希望能够直接把文件直接提交到服务器上更好,就像我们一般的Web应用上传附件一样感觉更好一些,后来发现了可以通过URL.createObjectURL进行相关的处理,参考了一些案例,对前面介绍的JSSDK的图片上传方式进行改良,从而实现了把图片附件通过表单的方式直接提交到自己后台服务器上了,下面开始介绍一下这种方式的思路和实现代码。

首先我们定义一个预览图片的列表和一个Input的文件控件元素,替代前面的做法,如下所示。

<divclass="weui-cells__title">症状图片</div>
<divclass="weui-cells weui-cells_form">
    <divclass="weui-cell">
        <divclass="weui-cell__bd weui-cell_primary">
            <divclass="weui-uploader">
                <!--预览图片的列表-->
                <ulclass="weui-uploader__files"id="imgSick">
                </ul>
                <divclass="weui-uploader__input-box">
                    <!--上传图片附件控件-->
                    <inputid="uploaderSick"class="weui-uploader__input"type="file"accept="image/*"multiple="">
                </div>
            </div>
        </div>
    </div>
</div>

为了实现选择图片文件的时候,预览图片的列表可以动态变化(动态增加 li 项目),我们需要定义对应的事件来实现这个操作。

        //存放文件图片的集合
        var fileSick = newArray();var filePres = newArray();functioninitImage() {var tmpl = '<li class="weui-uploader__file" style="background-image:url(#url#)"></li>',
$gallery
= $("#gallery"),
$galleryImg
= $("#galleryImg"),

$uploaderSick
= $("#uploaderSick"),
$imgSick
= $("#imgSick"),
$uploaderPres
= $("#uploaderPres"),
$imgPres
= $("#imgPres");//症状图片上传 $uploaderSick.on("change", function(e) {var src, url = window.URL || window.webkitURL ||window.mozURL,
files
=e.target.files;for (var i = 0, len = files.length; i < len; ++i) {var file =files[i];
fileSick.push(file);
//加入集合 if(url) {
src
=url.createObjectURL(file);
}
else{
src
=e.target.result;
}
$imgSick.append($(tmpl.replace(
'#url#', src)));
}
});

..............

我们注意到了,这里没有使用chooseImage的JSSDK接口,而是通过 url.createObjectURL(file) 的方式获取路径,展示在图片列表控件里面。

对于动态增加的图片,我们可以让它支持单击预览的方式,预览其实是把图片放在一个预览层里面。

    var index; //第几张图片
    var category;//那个类别
    var imgid;//图片ID
    //症状图片单击处理
    $imgSick.on("click", "li", function() {
index
= $(this).index();
category
= "sick";
imgid
= $(this).attr("id");
$galleryImg.attr(
"style", this.getAttribute("style"));
$gallery.fadeIn(
100);
});

预览层的DIV是放在主界面上的,主界面是一个放置图片的区域,底部是一个删除按钮,用来我们实现图片删除操作的。

<!--图片预览层-->
<divclass="weui-gallery"id="gallery">
    <spanclass="weui-gallery__img"id="galleryImg"style="width:auto"></span>
    <divclass="weui-gallery__opr">
        <ahref="javascript:"class="weui-gallery__del">
            <iclass="weui-icon-delete weui-icon_gallery-delete"></i>
        </a>
    </div>
</div>

预览层再次单击的时候关闭,执行的JS代码如下所示。

$gallery.on("click", function() {
$gallery.fadeOut(
100);
});

删除图片的时候,我们区分是存在服务器的图片,还是本地临时选择的图片,区别对待。如果服务器图片,需要提示确认删除,如果是本地临时图片,直接移除即可。

//删除图片(根据类别和序号处理)
$(".weui-gallery__del").click(function() {                
console.log(index
+ ',' + category + ',' + imgid);//记录显示 //如果是在服务端的图片,确认后移除 if (imgid != undefined && imgid != '') {
$.confirm(
"您确定要永久删除该图片吗?", "永久删除?", function() {var url = "/H5/DeleteAttachment?openid=@ViewBag.openid";var postData ={
id: imgid.replace(
/img_/, '') //控件id去掉前缀为真正附件ID };

$.post(url, postData,
function(json) {//转义JSON为对象 var data =$.parseJSON(json);if(data.Success) {
$.toptip(
"删除成功!", 'success');//在界面上找到对应控件ID,移除控件 RemoveImg();
}
else{
$.toast(
"操作失败:" + data.ErrorMessage, "forbidden");
}
});
});
}
else{
RemoveImg();
//普通图片快速移除 };
});

其中移除图片显示的JS代码如下所示。

//移除对应类别的图片
functionRemoveImg() {if (category == "sick") {
$imgSick.find(
"li").eq(index).remove();
fileSick.splice(index,
1);
}
else{
$imgPres.find(
"li").eq(index).remove();
filePres.splice(index,
1);
}
};

我们要使用表单上传文件的方式,就需要在JS里面创建一个FormData的对象,用来承载文件内容,如下所示

var formData = new FormData();//构建一个FormData存储复杂对象

如果是常规的表单数据,我们通过键值,把内容填入FormData即可,如下所示。

var formData = new FormData();//构建一个FormData存储复杂对象
formData.append("PatientName", $("#PatientName").val());

如果是图片附件的,我们则需要遍历集合文件,把它们逐一加入对应键值里面,为了区分不同的类别文件,我们使用不同的前缀方式,如下代码所示。

//加入症状图片
for (var i = 0; i < fileSick.length; i++){
formData.append(
"sick_" +i, fileSick[i]);
};
//加入处方图片 for (var i = 0; i < filePres.length; i++){
formData.append(
"pres_" +i, filePres[i]);
};
//提交表单数据和文件
var url = "/H5/DrugInquirySave2?openid=@ViewBag.openid";
$.ajax({
url: url,
type:
'post',
processData:
false,
contentType:
false,
data: formData,
success:
function(json) {//转义JSON为对象 var data =$.parseJSON(json);if(data.Success) {
$.toast(
"处方已提交审核中,稍后请到处方查询查看。");//WeixinJSBridge.call('closeWindow');//关闭窗口 location.href = "/h5/Prescription";//跳转到处方页面 }else{
$.toast(
"保存失败:" + data.ErrorMessage, "forbidden");
}
}
});

在后台的处理函数 DrugInquirySave2 里面,我们需要把文件按键名提取出来,根据文件键名的不同,放到不同给的集合里面存储起来即可。

如下是DrugInquirySave2 函数里面的部分代码,用来处理收到的表单文件集合。然后我们在把文件写入文件系统即可,这样省却了对JSSDK提交文件,再去微信服务器提取文件方式的麻烦,直接由客户端把文件上传的自己的文件服务器了。

#region 通过文件附件方式获取
var files =Request.Files;if (files != null && files.Count > 0)
{
LogTextHelper.Info(
string.Format("收到文件:{0}", files.Count));//测试 foreach (string key infiles.Keys)
{
LogTextHelper.Info(
string.Format("收到文件key:{0}", key));var fileData =files[key];bool isSickImage = key.ToLower().IndexOf("sick") >= 0;//判断是否为问诊图片分类 if (fileData != null)
{
HttpContext.Request.ContentEncoding
= Encoding.GetEncoding("UTF-8");
HttpContext.Response.ContentEncoding
= Encoding.GetEncoding("UTF-8");
HttpContext.Response.Charset
= "UTF-8";string fileName = Path.GetFileName(fileData.FileName); //原始文件名称 string fileExtension = Path.GetExtension(fileName); //文件扩展名 FileUploadInfo fileInfo= newFileUploadInfo();
fileInfo.FileData
=ReadFileBytes(fileData);if (fileInfo.FileData != null)
{
fileInfo.FileSize
=fileInfo.FileData.Length;
}
//判断图片类别分组 fileInfo.Category = isSickImage ? "问诊图片" : "处方图片";
fileInfo.FileName
=fileName;
fileInfo.FileExtend
=fileExtension;//判断属于那个分组【这里只有两个分组】 fileInfo.AttachmentGUID = isSickImage ?SickAttachGUID : PresAttachGUID;

fileInfo.AddTime
= DateTime.Now;//创建时间 fileInfo.Editor = openid;//记录人 fileInfo.Owner_ID = info.ID;//属于主表记录 result= BLLFactory<FileUpload>.Instance.Upload(fileInfo);if (!result.Success)
{
LogTextHelper.Error(
"上传文件失败:" +result.ErrorMessage);
}
}
}
}
#endregion

编辑现有记录的时候,也可以实现对已有图片的删除操作,临时文件的预览处理和再次上传等操作。

3、两种图片处理方式的总结

本篇随笔是基于公众号上传图片文件的两种方式的处理,分别是使用微信JSSDK和使用URL.createObjectURL上传预览图片的不同处理对比,两种方式都能够满足图片的处理操作。对比处理代码,可能使用后者可能更加简洁一些。而且微信浏览器对URL.createObjectURL的支持也非常不错,可以在微信开发工具和实际环境上都正常使用。

在开发微信应用的时候,我们往往需要确认用户的身份,一般公众号唯一区别用户的身份是openid信息,但是这个信息并不是可以直接获取到,需要通过code进行获取,而code的获取则需要用户进行一个授权的处理才能获得,本篇随笔通过结合Session的方式,自动判断用户状态,如果用户首次访问页面,则以重定向的方式实现用户身份信息的获取并转回原来页面。

1、常规的页面身份获取处理

之前为了在某个页面里面获取用户身份信息,需要把URL进行一个授权的处理URL,如下所示。

通过这样的方式处理,我们可以在页面处理里面,获得code参数,然后根据code参数获取openid。

string code = Request.QueryString["code"];
var result =baseApi.GetAuthToken(accountInfo.AppID, accountInfo.AppSecret, code);if (result != null && !string.IsNullOrEmpty(result.openid))
{
Session["openid"] = result.openid;//存储在Session }

其中 GetAuthToken 是我们根据微信API进行封装的一个通过code换取网页授权access_token的接口方法。

        /// <summary>
        ///通过code换取网页授权access_token///首先请注意,这里通过code换取的是一个特殊的网页授权access_token,与基础支持中的access_token(该access_token用于调用其他接口)不同。///公众号可通过下述接口来获取网页授权access_token。///如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。/// </summary>
        /// <param name="appId">公众号的唯一标识</param>
        /// <param name="appSecret">公众号的appsecret</param>
        /// <param name="code">code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。</param>
        /// <param name="grantType">填写为authorization_code</param>
        /// <returns></returns>
        public AccessTokenResult GetAuthToken(string appId, string appSecret, string code, string grantType = "authorization_code")
{
var key = code + "_AuthToken";
AccessTokenResult itemResult
= MemoryCacheHelper.GetItem<AccessTokenResult>(key);if (itemResult == null)
{
var url = string.Format("https://api.weixin.qq.com/sns/oauth2/access_token?appid={0}&secret={1}&code={2}&grant_type={3}",
appId, appSecret, code, grantType);
var authToken = WeJsonHelper<AccessTokenResult>.ConvertJson(url);
MemoryCacheHelper.AddItem(key, authToken);
//先加入一个获取的值 }//然后每次从其中取,如果超过时间则启用刷新机制 var access_token = MemoryCacheHelper.GetCacheItem<AccessTokenResult>(key, delegate()
{
returnRefreshAuthToken(appId, itemResult.access_token);
},
new TimeSpan(0, 0, 7000)//7000秒过期 );returnaccess_token;
}

这种方式能够正常获取openid,不过每个菜单这样进行URL处理,并且每次重复这样的逻辑获取openid,肯定不是什么好的办法。

因此考虑一种通用的方式来处理,以减少这些曲折处理过程。

2、通用函数处理,以重定向的方式实现用户身份信息

上面提出了,采用常规处理方式,菜单URL需要先转移,后台重复处理code的转换,非常不方便我们开发业务功能。

其实我们可以把以上获取用户身份的处理放置在一个通用函数里面,这样每次确保获得Openid即可,如果第一次访问页面,那么记录当前页面地址,并重定向到获取code的页面,并解析code为openid即可,然后放在Session里面存储起来,下次直接读取Session获取即可。

我们首先在入口页面里面记录当前页面地址,然后转去判断并获取openid即可。

如果在session里面没有获取到Openid,那么认为是第一访问页面,重新判断是否有code进来,如果没有,先获取code,然后转回到当前的 AuthOpenId 地址入口来。

/// <summary>
///通过重新转向,获取用户code并转换为openid。///用于自动获取当前用户的身份。/// </summary>
/// <returns></returns>
protectedActionResult AuthOpenId()
{
//先判断Session是否存在 var open_id = Session["openid"];if (open_id == null)
{
//如果第一次(没有code),则再次转到授权页面重新获取code string code = Request.QueryString["code"];if (string.IsNullOrEmpty(code))
{
var authUrl = baseApi.GetAuthorizeUrl(accountInfo.AppID, Request.Url.AbsoluteUri, "", OAuthScope.snsapi_base); Response.Redirect(authUrl);return null;
}

如果是已经获取到了code,则根据code进行解析获取openid,如下代码所示。

        else{//如果成功获取code,那么根据code获取openid
            var result =baseApi.GetAuthToken(accountInfo.AppID, accountInfo.AppSecret, code);if (result != null && !string.IsNullOrEmpty(result.openid))
{
//LogHelper.Info("openid:" + result.openid); Session["openid"] = result.openid;//存储在Session }
}

最后如果顺利获得openid,那么返回到最初入口的页面地址(已经存放地址在Session里面了)

    //获取返回的连接
    var backUrl = Session["back_url"];if (backUrl != null)
{
returnRedirect(backUrl.ToString());
}
else{return View("PersonalInfo");//返回个人页面 }

整个函数的完整的代码如下所示。

/// <summary>
///通过重新转向,获取用户code并转换为openid。///用于自动获取当前用户的身份。/// </summary>
/// <returns></returns>
protectedActionResult AuthOpenId()
{
//先判断Session是否存在 var open_id = Session["openid"];if (open_id == null)
{
//如果第一次(没有code),则再次转到授权页面重新获取code string code = Request.QueryString["code"];if (string.IsNullOrEmpty(code))
{
var authUrl = baseApi.GetAuthorizeUrl(accountInfo.AppID, Request.Url.AbsoluteUri, "", OAuthScope.snsapi_base);
Response.Redirect(authUrl);
return null;
}
else{//如果成功获取code,那么根据code获取openid var result =baseApi.GetAuthToken(accountInfo.AppID, accountInfo.AppSecret, code);if (result != null && !string.IsNullOrEmpty(result.openid))
{
Session[
"openid"] = result.openid;//存储在Session }
}
}
//获取返回的连接 var backUrl = Session["back_url"];if (backUrl != null)
{
returnRedirect(backUrl.ToString());
}
else{return View("PersonalInfo");//返回个人页面 }
}

这样我们在菜单里面,就不需要前面所说的转义函数处理了,所有的身份获取都通过标准操作确保获取用户的openid了。

页面的处理也变得相对容易一些,根据用户身份显示不同的视图页面。

/// <summary>
///患者问诊/// </summary>
/// <returns></returns>
publicActionResult DrugInquiry()
{
Session[
"back_url"] =Request.Url.AbsoluteUri;
AuthOpenId();
//自动获取当前用户的身份。 string openid = (string)Session["openid"];if (!string.IsNullOrEmpty(openid))
{
//刷新配置JS的参数 RefreshTicket();
ViewBag.openid
=openid;var userInfo = BLLFactory<User>.Instance.FindByOpenId(openid);if (userInfo != null)
{
//识别用户身份后的处理逻辑 ...............
}
return View("DrugInquiry");
}
else{
ViewBag.Title
= "无法获取用户身份信息";
ViewBag.Message
= "无法获取用户身份信息";
ViewBag.Type
= "error";return View("info");
}
}

以上就是微信开发中使用通用函数处理,以重定向的方式实现用户身份信息的获取并转回原来页面的做法,这个函数给我们减轻了很多繁琐的问题,并且减少了重复复制代码来获取用户身份的弊端,是我们在H5页面里面处理用户身份信息的利器,希望对大家在开发微信公众号或者企业微信,获取用户身份的时候,提供好的参考思路和代码。

在我们开发微信应用的时候,包括公众号、企业微信、小程序等,都需要基于一定的框架基础上开发,才能事半功倍,一般情况下,我们需要处理好微信菜单和微信事件的对接,以及后台数据管理系统和权限管理的有效整合,或者是基于业务流程的相关流转等,本篇随笔介绍如何基于微信开发框架进行业务开发的处理过程。

1、微信账号配置

在我们开发任何微信基础应用之前,都需要先录入微信账号的相关信息,包括appId,AppSecret等基础信息,这个是用来获取token,和微信服务器进行交互的基础,我们的微信框架提供了多种类型和多个账号的管理。

其中在新增或者编辑界面中录入我们公众号或者企业微信的信息,如果涉及微信支付的,录入微信支付相关秘钥和证书位置。

如果是企业微信,我们选择账号类型为企业微信,然后根据提示录入相关企业微信信息即可。

其中主要是CorpID和CorpSecret和企业的AgentId信息,以及加密token和秘钥,保持和微信后台信息一致即可。

我们微信开发框架,提供了标准的微信账号信息填写界面,录入对应的信息,完成微信后台的对接,即可开启开发微信应用之旅了。

账号登录公众号后台或者企业微信后台,我们根据和账号配置一致对应的信息,完成系统对接即可,以下是微信公众号的配置对接界面。

企业微信的对接类似,不在赘述。

2、微信菜单配置

为了开发微信应用,我们还是需要准备好微信应用的菜单,菜单是我们所有前端微信功能的入口,我们一般需要先在微信开发框架 基础上进行菜单的录入维护,然后在提交到微信服务器上,实现应用菜单的提交。

在微信开发框架上维护菜单,可以根据需要禁用、启用某个菜单,然后再通过微信SDK接口提交更新到服务器进行菜单的更新,我们可以对多个账号的微信菜单进行维护。

1)医疗设备维修的微信应用菜单

例如我在开发一个医疗设备维修的微信应用的时候,菜单定义可能是这样子。

这样我们提交菜单后,在公众号上就可以对相关的功能进行操作了。

相关的菜单对应功能如下所示。

2)药店处方管理的微信应用菜单

我们再来看看,另一个微信公众号应用的菜单管理

提交到服务器后,在应用入口的菜单如下所示。

3)企业微信应用菜单配置

由于微信开发框架也支持企业微信接入,因此我们也可以同时开发企业微信应用的对接,首先也是一样,定义好对应的企业微信菜单。

以下是我们开发的一个资产管理的企业微信应用,配置了相关的菜单如下所示。

提交成功后,我们就可以在企业微信的工作台上看到对应企业微信的菜单了。

3、机构、角色、用户的权限管理

微信应用,除了提供相应的菜单功能外,一般应用前端的H5页面或者后台页面都可能需要结合机构、角色、用户和其权限信息进行系统权限的判断和数据的处理。

例如我们针对医疗设备微信的应用,在微信后台系统里面定义了相关的机构、用户、角色等信息。

组织机构如下所示。

用户角色如下所示:

系统根据角色对菜单进行不同的显示设置。

而我们如果应用不同,在系统后台调整这些机构、角色、用户信息即可,如下面则是药店处方定义的机构信息。

用户角色定义如下所示。

根据不同的业务需要,定义自己的组织机构层次,角色列表和人员信息即可,有了这些信息,我们可以同时对用户系统的操作功能和H5前端的权限进行控制管理。

4、H5页面的开发

开发微信公众号或者企业应用,很多时候工作的时间需要花费在H5页面的开发上,H5页面的规范我们可以参考微信的Weui(公众号) 或者WeUI for work (企业微信),以及借用一些漂亮的图表,以及现成的一些H5案例界面。WeUI还可以参考另一款 基于JQuery的 jQuery WeUI, 提供更多的案例和插件。

WeUI :
https://github.com/Tencent/weui

WeUI for work:
WeUI for Work (企业微信版)

jQuery WeUI:
http://jqweui.com/

WeUI+ 演示
https://weui.shanliwawa.top/

常规情况下,我们可以根据上面的一些案例,构建我们的一些输入或者显示元素,如日期输入、选择输入、字典绑定、图片展示、文件上传等等。

一般来说,H5页面开发,需要准备一些漂亮一点的图标,可以让程序增色不少,图片可以在网站下载或者淘宝购买一些设计图标,根据相应场景进行使用即可。以下是我自己收集的一些图表,在需要的时候,找一些匹配的使用即可。

H5页面如果有一些标准的案例来参考,做起来可能更加得心应手,如下是我自己收集的一些H5案例,根据实际项目的需要,在找一些对应的页面来模仿修改即可。



根据一些界面的样式,我们设计了一些类似的界面效果。

医疗设备维修的界面设计效果。

医药处方审核界面设计效果。



对于图片预览以及一些特殊功能,我们使用JSSDK实现即可,而表单的数据提交,主要就是POST、GET的JS代码处理,这样可以实现动态的数据绑定和处理,特别是在查询分页展示的时候,使用ajax的处理,可以提高体验效果。

最近,物联网的概念比较热门,一大批厂商抢着占领物联网的高低,包括有华为物联网、阿里云物联网、腾讯物联网、AWS物联网等等,无法一一列举,一般物联网包含设备侧开发、平台侧开发、应用侧开发,三个部分构成了线上线下的完整连接,和我们常规的微信应用、钉钉应用等不同,物联网的终端是由各种各样的设备组合而成,这些设备通过各种不同的协议(如CoAP,LWM2M、MQTT)连接到IOT的平台,而且这些设备是低能耗的设备,可以实时的发送数据上来,也可以接受来自IOT平台下发的各种操作指令。本篇随笔主要介绍基于华为物联网IOT的应用开发,实现对.net SDK的封装,方便后期进行应用集成使用。

1、物联网的相关介绍

物联网其实有点类似我们以前做的一些行业设备的接入,不过它相对比较通用化一些,可以连接各种各样的类型设备,而且更加安全、低能耗等,我们以前接入很多设备,可能需要走TCP/UDP协议,然后在后台服务器有一个对这些设备管理的一个Socket服务器,不过和物联网对比,这些都被彻底改造过了,以便适应更多的 应用场景,更简化的开发,以及支持更强大的功能吧。

物联网目前可以针对一些传感器采集一些特定的参数,如光感、温度、湿度、压力、电压、电流等常规的信息,也可以扩展实现语音、图像、视频等方面的采集处理,如经典的智慧路灯应用场景。

下面是其中的一个应用的架构设计,主要就是针对这些设备管理,物联网还提供了很多完善的应用API接口,使得我们可以更加简化对设备的管理(不用架设Socket服务),更加方便的通过API获取相应的信息,节省更多的维护成本。

物联网平台支持海量设备连接上云,设备与云端可以实现稳定可靠地双向通信。

  • 提供设备端SDK、驱动、软件包等帮助不同设备、网关轻松接入物联网云。
  • 提供2G/ 3G /4G、NB-IoT、LoRa、WiFi等不同网络设备接入方案,解决企业异构网络设备接入管理痛点。
  • 提供MQTT、CoAP、HTTP/S等多种协议的设备端SDK,既满足长连接的实时性需求,也满足短连接的低功耗需求。
  • 开源多种平台设备端代码,提供跨平台移植指导,赋能企业基于多种平台做设备接入。

一般的物联网平台,都会包括产品管理、设备管理、设备组管理、规则引擎管理、消息推送和消息订阅、任务管理、设备升级等等,不同的物联网云平台有所不同。

物联网的几个相关的协议:

MQTT(Message Queue Telemetry Transport)

MQTT是一个物联网传输协议,被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。

MQTTS指MQTT+SSL/TLS,在MQTTS中使用SSL/TLS协议进行加密传输。

CoAP(Constrained Application Protocol)

受约束的应用协议(CoAP)是一种软件协议,旨在使非常简单的电子设备能够在互联网上进行交互式通信。

CoAPS指CoAP over DTLS,在CoAPS中使用DTLS协议进行加密传输。

LWM2M(lightweight Machine to Machine)

LWM2M是由OMA(Open Mobile Alliance)定义的物联网协议,主要使用在资源受限(包括存储、功耗等)的NB-IoT终端

2、应用侧开发接口

应用侧的开发接口一般云平台都会提供不同平台的SDK,如阿里云开源提供Java SDK/C# SDK等;而华为则提供了Java、PHP等SDK,没有包含.net 的SDK。

阿里物联网云的应用侧API接口包括:

华为物联网云的应用侧API接口包括:

本篇主要介绍最基础的物联网SDK的包装,以方便后续的应用开发集成。本篇随笔也主要是基于华为应用侧API的封装,使用C#语言实现对.net SDK的全部封装处理。

针对上面的接口分类,我们定义不同的接口类来处理它们。

基本上所有API访问都先需要通过鉴权接口获取访问的token,鉴权接口定义如下所示。

而且华为的API接口,需要使用X509证书处理的,我们可以通过在官网下载对应的X509证书进行集成测试API。

为了实现对API进行的.net SDK封装,我们定义一些系统常见变量,方便在接口中使用。

根据鉴权返回的结果,我们定义一个对应的实体类来存储这些属性信息,如下所示。

    /// <summary>
    ///鉴权结果/// </summary>
    public classAuthenticationResult : BaseJsonResult
{
/// <summary> ///申请权限范围,即accessToken所能访问物联网平台资源的范围,参数值固定为default。/// </summary> public string scope { get; set; }/// <summary> ///accessToken的类型,参数值固定为Bearer 。/// </summary> public string tokenType { get; set; }/// <summary> ///accessToken的有效时间,参数值固定为3600秒/// </summary> public int expiresIn { get; set; }/// <summary> ///鉴权参数,访问物联网平台API接口的凭证。/// </summary> public string accessToken { get; set; }/// <summary> ///鉴权参数,有效时间为1个月,用于“刷新Token”接口。///当accessToken即将过期时,可通过“刷新Token”接口来获取新的accessToken。/// </summary> public string refreshToken { get; set; }
}

然后根据鉴权定义,我们实现对这个接口的封装处理。

    /// <summary>
    ///IOT鉴权接口实现/// </summary>
    public classAuthenticationApi : IAuthenticationApi
{
/// <summary> ///用户鉴权///应用服务器首次访问物联网平台的开放API时,需调用此接口完成接入认证;///应用服务器在物联网平台的认证过期后,需调用此接口重新进行认证,才能继续访问物联网平台的开放API。/// </summary> publicAuthenticationResult Authentication()
{
string postData = string.Format("appId={0}&secret={1}", Constants.AppId, Constants.Secret);var url = Constants.AppBaseUrl + "/iocm/app/sec/v1.1.0/login";//名称大小写不能错 HttpHelper helper= newHttpHelper();
helper.ContentType
= "application/x-www-form-urlencoded";
helper.ClientCertificates
= new X509CertificateCollection() { newX509Certificate2(Constants.CertFilePath, Constants.CertPassword) };string content = helper.GetHtml(url, postData, true);var result = JsonConvert.DeserializeObject<AuthenticationResult>(content);returnresult;
}

对于Token的刷新操作,封装也是类似操作

        /// <summary>
        ///刷新token。///应用服务器通过鉴权接口获取到的accessToken是有有效时间的,在accessToken快过期时,///应用服务器通过调用此接口,获取新的accessToken。/// </summary>
        /// <param name="refreshToken">
        ///刷新token,用来获取一个新的accessToken。refreshToken在调用鉴权接口或刷新token接口时获得。/// </param>
        /// <returns></returns>
        public AuthenticationResult RefreshToken(stringrefreshToken)
{
string postData = new{
appId
=Constants.AppId,
secret
=Constants.Secret,
refreshToken
=refreshToken
}.ToJson();
var url = Constants.AppBaseUrl + "/iocm/app/sec/v1.1.0/refreshToken";//名称大小写不能错 var result = WeJsonHelper<AuthenticationResult>.ConvertJson(url, postData);returnresult;
}

上面有些是为了方便操作,定义了一些公用类库,以减少代码的重复。

一般除了鉴权外的所有的API,它们处理方式都类似的,都是以 application/json 格式进行交互,使用POST、PUT、GET、Delete操作方式实现对数据的处理。

而且它们都需要有固定的请求头信息,我们以设备注册为例进行介绍。

一般我们可以通过一个函数封装,对接口的头部请求信息进行设置,如下所示。

        /// <summary>
        ///通用获取头部信息/// </summary>
        /// <param name="accessToken">接口访问口令</param>
        /// <returns></returns>
        protected NameValueCollection GetHeader(stringaccessToken)
{
var header = newNameValueCollection();
header.Add(Constants.HEADER_APP_KEY, Constants.AppId);
header.Add(Constants.HEADER_APP_AUTH,
string.Format("Bearer {0}", accessToken));returnheader;
}

然后在定义请求的JSON和返回的JSON对应的实体类对象,封装对应的API接口即可。

例如,我们注册设备的接口封装如下所示。

    /// <summary>
    ///设备管理接口实现/// </summary>
    public classDeviceApi : BaseCommon, IDeviceApi
{
/// <summary> ///注册设备(验证码方式)。///在设备接入物联网平台前,应用服务器需要调用此接口在物联网平台注册设备,并设置设备的唯一标识(如IMEI)。///在设备接入物联网平台时携带设备唯一标识,完成设备的接入认证。/// </summary> /// <param name="accessToken"></param> /// <param name="info"></param> /// <returns></returns> public RegDeviceResult RegisterDevice(stringaccessToken, RegDeviceJson info)
{
var header =GetHeader(accessToken);string postData =info.ToJson();var url = Constants.AppBaseUrl + "/iocm/app/reg/v1.1.0/deviceCredentials";//名称大小写不能错 url += string.Format("?appId={0}", Constants.AppId);var result = WeJsonHelper<RegDeviceResult>.ConvertJson(url, postData, header);returnresult;
}
......

这里请求的信息是 RegDeviceJson , 返回信息的类是RegDeviceResult 。我们依照API定义,实现对应的处理即可。

为了方便处理,我们可以把这些对应设备的实体类定义在一个文件里面,如DeviceJson.cs里面,如下所示,这样非常方便我们管理。

其他业务范畴的Api也是如此封装,不在一一赘述。

我们测试的时候,可以建立一个单独的Winform项目进行接口功能的测试,也可以自己编写单元测试代码进行测试,根据自己熟练程度选择吧。

例如鉴权接口测试代码如下所示,我们可以看看输出进行判断是否正常工作。

        private void btnLogin_Click(objectsender, EventArgs e)
{
var result =basicApi.Authentication();
Console.WriteLine(result
!= null ? "accessToken:" + result.ToJson() : "获取结果出错");if (result != null)
{
var refreshResult =basicApi.RefreshToken(result.refreshToken);

Console.WriteLine(refreshResult
!= null ? "accessToken:" + refreshResult.ToJson() : "获取结果出错");this.accessToken = refreshResult.accessToken;//记录待用 }
}

设备注册的功能测试如下所示。

        private void btnRegDevice_Click(objectsender, EventArgs e)
{
if (string.IsNullOrEmpty(accessToken))
{
MessageUtil.ShowTips(
"请先鉴权获取AccessToken");return;
}
var deviceApi = newDeviceApi();var regDeviceInfo = newRegDeviceJson()
{
endUserId
= "64bf5869-b271-4007-8db8-fab185e19c10",
nodeId
= "64bf5869-b271-4007-8db8-fab185e19c10",
psk
= "12345678",
timeout
= 0,
verifyCode
= "",
deviceInfo
= newDeviceJson()
{
deviceType
= "Smoke",
manufacturerId
= "49ac78c99f3e453598c155870efe8bfc",
manufacturerName
= "iqidi",//与manufacturerId、manufacturerName、deviceType、model和protocolType系列参数二选一//productId = "5d9bf49b6a018f02d04cae28", model = "1001",
name
= "NBSimulator",
imsi
= "fafafasfasf",
mac
= "testetst",
isSecurity
= true.ToString().ToUpper(),
protocolType
= "LWM2M",
}
};
var regResult =deviceApi.RegisterDevice(accessToken, regDeviceInfo);
Console.WriteLine(regResult
!= null ? regResult.ToJson() : "no regResult");
}

另外对于事件的通知,我们一般是在应用端被动的进行相应的处理,因此需要对它们的消息进行转换和处理。

针对以上的消息通知,我们需要定义对应的消息类型,然后进行判断处理。

我们另起一个项目,然后定义对应给的事件接收处理,如下所示是一个统一的入口处理。

有了一个总入口,我们把对应通知的信息转换为对应的实体后,就可以进行记录或者响应的处理了。

在后面我们接着开发应用功能的时候,这些对应的接口API就可以集成整合在我们的系统中了。

以上就是对于华为物联网IOT平台应用侧的API封装处理的思路, 供大家参考交流。