当前位置: 首页 > news >正文

PI Square中文论坛: PI SDK 开发中级篇| PI Square

注: 为了更好的利用站内资源营造一个更好的中文开发资源空间,本文为转发修正帖,原作者为OSIsoft技术工程师王曦(Xi Wang),原帖地址:PI SDK 中级篇

 来源:https://d.gg363.site/url?sa=t&rct=j&q=&esrc=s&source=web&cd=4&ved=2ahUKEwjJ3-HLrP3eAhXJULwKHWSqBloQFjADegQIBxAB&url=https%3A%2F%2Fpisquare.osisoft.com%2Fcommunity%2Fall-things-pi%2Fchinese%2Fblog%2F2016%2F08%2F19%2Fpi-sdk-%25E5%25BC%2580%25E5%258F%2591%25E4%25B8%25AD%25E7%25BA%25A7%25E7%25AF%2587&usg=AOvVaw0su5H-KyXXiNMxqaueXYFa

 

本帖旨在介绍使用PI SDK做一些基本的数据分析,同时,也包括了数据更新的方法,和一些推荐的程序结构。

本帖针对已对PI SDK基础篇比较了解的开发人员。由于OSIsoft在.NET环境下的开发包,已基本由AF SDK取代,因此,本帖只使用C++语言作为PI SDK的开发平台。如果您需要在.NET环境中进行二次开发,请参考AF SDK中级篇。

说明:PI SDK 是过时的技术

 

1. 准备工作

 

在这里的第一段程序,是推荐使用的,进行PI服务器的连接工作,是用子程序的调用方式:

 

 
  1. static ServerPtr PIServerConnect(_bstr_t servername)   
  2. {  
  3.     ::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);                  // 初始化COM              
  4.     IPISDKPtr spPISDK                                                 // 创建PI SDK连接  
  5.     spPISDK.CreateInstance(__uuidof(PISDK));                          // 实例化PI SDK连接  
  6.     ServerPtr spServer = spPISDK->GetServers()->GetItem(servername);  // 通过参数,获取连接  
  7.     return spServer;                                                  // 返回已连接的服务器指针  
  8. }  

进行数据的基本分析,需要搜索PI服务器内的点的数据,以下两端子程序,来自PI基础篇的所有点名和搜索点表的子程序:

 

按点名搜索:

 

 
  1. static PIPointPtr GetPIPointsByName(ServerPtr server, _bstr_t tagname)  
  2. {  
  3.     return server->PIPoints->GetItem(tagname);  // 返回一个PIPoint类型的指针  
  4. }  

 

按点表搜索:

 

 
  1. static _PointListPtr SearchPIPoints(ServerPtr server, _bstr_t condition)  
  2. {  
  3.    return server->GetPoints(condition, NULL); //返回PointList指针类  
  4. }  

 

2. Variant类型转换: 这是非常重要的部分,后面所有函数的讲解,都要依据此部分的功能

 

PI SDK的大部分函数所需要的参数,都要转换成variant类型,有的传递variant指针,有的传递引用,有的传递二级指针。下面的转换工作将为您展示如何将字符串类型的指针转换成variant类型:

在C++中,字符串指针一般会使用bstr指针类,我们使用这个类型作为例子,进行转换:

 

 
  1. _bstr_t start = "*-2h";                       // 字符串类起始时间  
  2. _variant_t starttime = (_variant_t)start;     // variant类起始时间  

 

上面是比较简单的方法,直接做的指针类型强制转换。

下面是做更加通用的方法:

 

 
  1. _bstr_t start = "*-2h";                                   //起始时间字符串  
  2. _PITimeFormatPtr spStart;                                 // 定义PI时间格式指针  
  3. spStart.CreateInstance(__uuidof(PITimeFormat));           // 指针实例化  
  4. spStart->InputString = start;                             // 指针指向起始时间字符串  
  5.   
  6.   
  7. VariantInit                                              // 初始化variant指针  
  8. V_VT (&starttime) = VT_DISPATCH;                         // 在variant内部类中进行通用指针转换  
  9. V_DISPATCH(&starttime) = spStart;                        // 使用dispatch函数,将variant指针指向PI时间格式指针  
  10. 除了PI的时间,PI的服务器名,PI点名等等,基本都是用这种方法进行格式转换。  

 

有了这部分内容后,后面各个函数将省略参数类型转换的功能。

 

功能一:取某一时间段的值(对应PI Datalink中的compressed data功能)

 

 
  1. static _PIValuesPtr CompressedData (PIPointPtr spPoint, _variant_t starttime, _variant_t endtime)  
  2. {      
  3.     return spPoint->Data->RecordedValues(&starttime, &endtime, BoundaryTypeConstants::btAuto, "", FilteredViewConstants::fvRemoveFiltered, NULL);    
  4. }  

 

这个函数看似简单,但其中的参数需要说明:

a. 参数starttime和endtime,都是variant &(引用)

b. BoundaryTypeConstants和FilteredViewConstants分别对应的功能就是PI Datalink中的边界类型和标记过滤值的功能

c. 比较不明显的,在参数中,有""参数,它代表的就是PI Datalink中的过滤条件,因为现在为测试,所以过滤条件在这里没有体现

 

功能二:按标准时间间隔显示数据(对应PI Datalink中的采样数据)

 

 

 
  1. static _PIValuesPtr SampledData (PIPointPtr spPoint, _variant_t starttime, _variant_t endtime)  
  2. {      
  3.     return sampled->InterpolatedValues2(&starttime, &endtime, &vtinterval, "", FilteredViewConstants::fvRemoveFiltered, NULL);  
  4. }  

 

 

此使用的参数与前面一个基本相同,只是多了一个&vtinterval,这个参数同样是variant的引用,意义是采样频率。

 

功能三:数据计算,这部分使用数据在一段时间内,以一个采样频率求和的功能,其他的,类似最大值,最小值等,基本都是使用类似的方法

 

 
  1. void GetSummariesValues(PIPointPtr spPIPoint, _variant_t vtStart, _variant_t vtEnd, _bstr_t interval)  
  2. {  
  3.     IPIData2Ptr ipdata2 = (IPIData2Ptr)spPIPoint->Data;                         // 使用PIData2接口类指针  
  4.     _NamedValuesPtr summary = ipdata2->Summaries2(vtStart, vtEnd,interval, ArchiveSummariesTypeConstants::asTotal,CalculationBasisConstants::cbEventWeighted,NULL);  // 定义NamedValues指针类  
  5.     _variant_t reference = "Total";                                                              
  6.     VARIANT vt_Item = reference;                                                                 // 转换指针为引用  
  7.     NamedValuePtr total = summary -> GetItem(&vt_Item);  
  8.     spPIValues = (_PIValuesPtr)total->Value;  
  9. }  

 

这个函数略微有点复杂,原因在于,需要计算的,如和,最大值,最小值,方差等的信息,都存在NamedValues指针类。同时,我们看到了variant指针和variant引用之间的转换方式。

在NamedValue指针类中,使用summary函数,将给定PI点按照时间段和采样频率进行求和。

 

功能四:取值

 

刚才所有的功能,返回的值都是PIValues,也就是类似于一个数组,下面的功能是遍历这个数组中的每一个数:

 

 
  1. for(long i = 1; i <= spPIValues->Count; i++)  
  2. {  
  3.     _PIValuePtr spPIValue = spPIValues->GetItem(i);  
  4. }  

 

这个做法很通俗,就不多讲了

 

功能五:数据更新,在此,默认数据类型是浮点型32位

 

 

 
  1. HRESULThr = spPIPoint->Data->UpdateValues(spPIValues, DataMergeConstants::dmInsertDuplicates, NULL);  

 

数据更新的功能是向PI服务器更新或插入数据,这个函数的使用需要比较小心。

首先,在使用这个函数之前,接口与数据源的数据传递应符合数据源的数据传递协议。当数据到达快照之后,应先使用_PIValuePtr spPIValue = spPIPoint->Data->GetSnapshot()获取点的数据;之后使用

spPIValues->put_ReadOnly( false )将PIValues指针类的写权限打开;然后, spPIValues->Add( "*" ,spPIValue->Value.fltVal + 1,spNVValAttr)将刚才的值写入PIValues指针类;最后, spPIValues->put_ReadOnly( true )将只读打开。

经过上述描述,相信大家已经明白数据更新的过程了。需要说明的是,PIValues指针类可以容纳很多的数据,也就是说,UpdateValues可以支持多点的同时更新。

除了数据的插入,这个函数还可以用作数据替换。您可能已经注意到了dmInsertDuplicates这个参数,同样,如果这个参数被替换成:dmReplaceDuplicates,那么,实现的功能就是替换给定时间的数据。这个时间的设定,就是在spPIValues->Add("*",spPIValue->Value.fltVal + 1,spNVValAttr)中,“*” 表示当前时间,同样,可以使用具体的时间戳进行替换,不过必不可少的就是variant类型的转换。

 

功能六:数据输出更新

 

PI系统的数据传输更新用于向外发送数据,主要使用EVENTPIPE这个工具。如果使用之传输数据,要分两步走

 

1. 创建EVENTPIPE

 

 
  1. static IEventPipe2Ptr Get_EventPipe (_PointListPtr spPointList)  
  2. {  
  3.     IEventPipe2Ptr spEventPipe2 = (IEventPipe2Ptr)spPointList->Data->EventPipe;       // 需要使用 IEventPipe2Ptr类型的指针,并且需要已经定义好的点表作为参数,用来明确需要哪些点的数据更新  
  4.     spEventPipe2->PollInterval = 2000;                                                // 数据更新频率,单位毫秒  
  5.     return spEventPipe2;                                                              // 返回这个指针  
  6. }  

 

2. 获取数据:

 

 
  1. void GetValue_EventPipe (EventPipePtr spEventPipe)  
  2. {  
  3.     while (spEventPipe->Count > 0)  
  4.     {  
  5.         _PIEventObjectPtr spEventObject = spEventPipe->Take();             // 定义一个PIEventObject类型的指针,获取刚才定义好的EVENTPIPE中的数据  
  6.         PointValuesPtr spPointValue = spEventObject->EventData;            // 将这个数据传递给PointValues指针参数  
  7.     }  
  8. }  

 

EVENTPIPE的作用就像一个队列,可以将不同点,不同时间的数据进行存储,当有客户端需要数据时,就把这些数据一次性直接给这个客户端。

 

注释一:PI服务器的值,在C++中的处理

 

PI中存储的值,在C++中是以variant类型存在的,因此,如果需要普通类型的值,可以使用如下的例子,这个例子是OSIsoft德国办公室资深工程师Andreas写的,您可以浏览他的博客,本贴只是加中文注释

 

MyPIValue::MyPIValue (_PIValuePtr pv) {                                                                                            // 将PI的值指针传递进该类,并且对值指针中所包含的内容进行归类分解
       codtTimeStamp = pv->TimeStamp->LocalDate;                                                                                
       bstrTimeStamp = (_bstr_t)codtTimeStamp.Format(_T("%d-%b-%Y %H:%M:%S"));
       DigitalStatePtr tmpDigitalState = NULL;
       IDispatchPtr    tmpDispatch = NULL;
       _PITimePtr      tmpPITime = NULL;
       COleDateTime    tmpTS;
       HRESULT         hr = E_FAIL;

 

       _variant_t vT = pv->Value;                                                                                                              // 过去值指针中的点的数据
       vt = vT.vt;

 

       switch (vT.vt) {
       case VT_I4:                                                                                                                                      // variant VT_I4类存储的是整形32位
              // Int32
              intValue = vT.lVal;
              dblValue = intValue;
              bstrValue = (_bstr_t)intValue;
              break;
       case VT_I2:                                                                                                                                      // variant VT_I2类存储的是整形16位
              // Int16
              intValue = vT.iVal;
              dblValue = intValue;
              bstrValue = (_bstr_t)intValue;
              break;
       case VT_R8:                                                                                                                                    // variant VT_R8类存储的是浮点形64位
              // Float64
              dblValue = vT.dblVal;
              intValue = (int)dblValue;
              bstrValue = (_bstr_t)dblValue;
              break;
       case VT_R4:                                                                                                                                    // variant VT_R4类存储的是浮点形32位
              // Float16/Float32
              dblValue = vT.fltVal;
              intValue = (int)dblValue;
              bstrValue = (_bstr_t)dblValue;
              break;
       case VT_BSTR:                                                                                                                              // variant VT_BSTR类存储的是字符串类
              // String
              bstrValue = vT.bstrVal;
              dblValue = 0;
              intValue = 0;
              break;
       case VT_DISPATCH:                                                                                                                      // variant VT_DISPATCH类存储的是数字类型,这是最复杂的
              // Digital?                                                                                                                                   // 首先需要拿到数字类型表示的内容
              tmpDispatch = vT.pdispVal;
              hr =  tmpDispatch.QueryInterface(__uuidof(DigitalState),&tmpDigitalState);
              if (hr == S_OK) {
                     bstrValue = tmpDigitalState->Name;
                     intValue = tmpDigitalState->Code;
                     dblValue = intValue;
              }
              // Timestamp?                                                                                                                           // 然后然后获取数字类型值的时间戳
              hr =  tmpDispatch.QueryInterface(__uuidof(_PITime),&tmpPITime);
              if (hr == S_OK) {
                           tmpTS = tmpPITime->LocalDate;
                           bstrValue = (_bstr_t)tmpTS.Format(_T("%d %B %Y %H:%M:%S"));
                           intValue = 0;
                           dblValue = 0;
              }
              break;
       default :
              dblValue = 0.0;
              intValue = 0;
              bstrValue = "n/a";
              break;
       }
};

注释二:后续工作---指针清空,关闭COM

 

为了保证没有内存泄露的情况,在程序的最后,需要清空指针,还要进行:

 

 
  1. ::CoUninitialize();  

 

用于关闭COM LIBARAY

 

以上是各个功能模块的介绍,下面是一个用PI SDK进行求和工作的完整程序,也是推荐的程序结构方式:

 

 
  1. #include "stdafx.h"                                      
  2. #include <iostream>   
  3. #include <string>  
  4. #include "ATLComTime.h" //for COleDateTime  
  5.   
  6. #import "C:\Program Files\PIPC\PISDK\PISDKCommon.dll" no_namespace  
  7. #import "C:\Program Files\PIPC\PISDK\PITimeServer.dll" no_namespace  
  8. #import "C:\Program Files\PIPC\PISDK\PISDK.dll" rename("Connected", "PISDKConnected") no_namespace  
  9. VOID WINAPI Sleep(_In_ DWORD dwMillisecons);                                                                                                                          // 以上为程序头文件  
  10.   
  11. class MyPIValue                                                                                                                                                                                       // 建立一个PIValue的默认类  
  12. {  
  13.     _PIValuePtr spPIValue;  
  14. public:  
  15.     MyPIValue (_PIValuePtr);  
  16.     double dblValue;  
  17.     int intValue;  
  18.     _bstr_t bstrValue;  
  19.     _bstr_t bstrTimeStamp;  
  20.     COleDateTime codtTimeStamp;  
  21.     VARTYPE vt;  
  22.   
  23. };  
  24.   
  25. MyPIValue::MyPIValue (_PIValuePtr pv) {                                                                                                                                               // 建立一个翻译PIValue的类  
  26.        codtTimeStamp = pv->TimeStamp->LocalDate;  
  27.        bstrTimeStamp = (_bstr_t)codtTimeStamp.Format(_T("%d-%b-%Y %H:%M:%S"));  
  28.        DigitalStatePtr tmpDigitalState = NULL;  
  29.        IDispatchPtr    tmpDispatch = NULL;  
  30.        _PITimePtr      tmpPITime = NULL;  
  31.        COleDateTime    tmpTS;  
  32.        HRESULT         hr = E_FAIL;  
  33.   
  34.        _variant_t vT = pv->Value;  
  35.        vt = vT.vt;  
  36.   
  37.        switch (vT.vt) {  
  38.        case VT_I4:  
  39.               // Int32  
  40.               intValue = vT.lVal;  
  41.               dblValue = intValue;  
  42.               bstrValue = (_bstr_t)intValue;  
  43.               break;  
  44.        case VT_I2:  
  45.               // Int16  
  46.               intValue = vT.iVal;  
  47.               dblValue = intValue;  
  48.               bstrValue = (_bstr_t)intValue;  
  49.               break;  
  50.        case VT_R8:  
  51.               // Float64  
  52.               dblValue = vT.dblVal;  
  53.               intValue = (int)dblValue;  
  54.               bstrValue = (_bstr_t)dblValue;  
  55.               break;  
  56.        case VT_R4:  
  57.               // Float16/Float32  
  58.               dblValue = vT.fltVal;  
  59.               intValue = (int)dblValue;  
  60.               bstrValue = (_bstr_t)dblValue;  
  61.               break;  
  62.        case VT_BSTR:  
  63.               // String  
  64.               bstrValue = vT.bstrVal;  
  65.               dblValue = 0;  
  66.               intValue = 0;  
  67.               break;  
  68.        case VT_DISPATCH:  
  69.               // Digital?  
  70.               tmpDispatch = vT.pdispVal;  
  71.               hr =  tmpDispatch.QueryInterface(__uuidof(DigitalState),&tmpDigitalState);  
  72.               if (hr == S_OK) {  
  73.                      bstrValue = tmpDigitalState->Name;  
  74.                      intValue = tmpDigitalState->Code;  
  75.                      dblValue = intValue;  
  76.               }  
  77.               // Timestamp?  
  78.               hr =  tmpDispatch.QueryInterface(__uuidof(_PITime),&tmpPITime);  
  79.               if (hr == S_OK) {  
  80.                            tmpTS = tmpPITime->LocalDate;  
  81.                            bstrValue = (_bstr_t)tmpTS.Format(_T("%d %B %Y %H:%M:%S"));  
  82.                            intValue = 0;  
  83.                            dblValue = 0;  
  84.               }  
  85.               break;  
  86.        default :  
  87.               dblValue = 0.0;  
  88.               intValue = 0;  
  89.               bstrValue = "n/a";  
  90.               break;  
  91.        }  
  92. };  
  93.   
  94.   
  95.   
  96. IPISDKPtr       spPISDK = NULL;            /* The PISDK */                                                                                              // 初始化所有需要用的指针  
  97. PISDKVersionPtr spSDKVersion = NULL;       /* PI SDK Version */  
  98. ServerPtr       spServer = NULL;           /* The Server */  
  99. PIPointPtr      spPIPoint = NULL;          /* The PI Point */  
  100. _PIValuePtr     spPIValue = NULL;   
  101. _PIValuesPtr     spPIValues = NULL;        /* The PI value */  
  102. _PITimeFormatPtr spStartTime = NULL;  
  103. _PITimeFormatPtr spEndTime = NULL;  
  104.   
  105. void GetSummariesValues(PIPointPtr spPIPoint, _variant_t vtStart, _variant_t vtEnd, _bstr_t interval)                                // 创建子函数  
  106. {  
  107.     IPIData2Ptr ipdata2 = (IPIData2Ptr)spPIPoint->Data;   
  108.     _NamedValuesPtr summary = ipdata2->Summaries2(vtStart, vtEnd,interval, ArchiveSummariesTypeConstants::asTotal,CalculationBasisConstants::cbEventWeighted  
  109.         ,NULL);  
  110.     _variant_t reference = "Total";  
  111.     VARIANT vt_Item = reference;  
  112.     NamedValuePtr total = summary -> GetItem(&vt_Item);  
  113.     spPIValues = (_PIValuesPtr)total->Value;  
  114.     for (long i = 1; i <= spPIValues->Count; i++)  
  115.     {  
  116.         spPIValue = spPIValues->GetItem(i);  
  117.         MyPIValue t(spPIValue);  
  118.         std::cout << t.bstrTimeStamp << " ";  
  119.         std::cout << t.bstrValue << std::endl;  
  120.     }  
  121.      total.Release();  
  122.      summary.Release();  
  123. }  
  124.   
  125.   
  126. int _tmain(int argc, _TCHAR* argv[])  
  127. {  
  128.        // Initialize COM  
  129.        ::CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);  
  130.        // Check the command line switches  
  131.        if (argc < 6) {  
  132.               std::cout << "Command Line:" << std::endl  
  133.                         << (_bstr_t)argv[0] << " SERVERNAME TAGNAME starttime endtime interval";  
  134.               return (1);  
  135.        }  
  136.        try                                     
  137.        {  
  138.               // Create an instance of the PI SDK                                                                                              // 主函数中连接PI服务器,也可使用子函数调用的方式  
  139.               spPISDK.CreateInstance(__uuidof(PISDK));  
  140.               // Print out the PI SDK version  
  141.               spSDKVersion = spPISDK->PISDKVersion;  
  142.               std::cout << std::endl << "PI-SDK Version "  
  143.                         << spSDKVersion->Version << " Build "  
  144.                         << spSDKVersion->BuildID << std::endl;  
  145.               // get the PI Server  
  146.               spServer = spPISDK->GetServers()->GetItem((_bstr_t)argv[1]);                                                                    // 从输入参数1中获取PI服务器名  
  147.               spPIPoint = spServer->PIPoints->GetItem((_bstr_t)argv[2]);                                                                          // 从输入参数2中获取点名  
  148.               spStartTime.CreateInstance (__uuidof(PITimeFormat));  
  149.               spEndTime.CreateInstance (__uuidof(PITimeFormat));  
  150.               spStartTime->InputString = argv[3];                                                                                                                     // 从输入参数3中获取起始时间  
  151.               spEndTime->InputString = argv[4];                                                                                                                      // 从输入参数4中获取截止时间  
  152.               _bstr_t interval = argv[5];                                                                                                                                        // 从输入参数5中获取采样频率  
  153.   
  154.                 
  155.               _variant_t vtStart;  
  156.               VariantInit (&vtStart);  
  157.               V_VT (&vtStart) = VT_DISPATCH;  
  158.               V_DISPATCH(&vtStart) = spStartTime;  
  159.   
  160.               _variant_t vtEnd;  
  161.               VariantInit (&vtEnd);  
  162.               V_VT (&vtEnd) = VT_DISPATCH;  
  163.               V_DISPATCH(&vtEnd) = spEndTime;  
  164.   
  165.               GetSummariesValues(spPIPoint, &vtStart, &vtEnd, interval);  
  166.               // You can use more than just one tagname  
  167.               /*for (int ii = 2; ii< argc; ii++) { 
  168.                      // Tagname 
  169.                      std::cout << (_bstr_t)argv[ii] << std::endl; 
  170.                      spPIPoint = spServer->PIPoints->GetItem((_bstr_t)argv[ii]); 
  171.                      // Snapshot 
  172.                      spPIValue = spPIPoint->Data->Snapshot; 
  173.                      MyPIValue mPV(spPIValue); 
  174.                      std::cout << mPV.bstrTimeStamp << " "; 
  175.                      std::cout << mPV.bstrValue << std::endl; 
  176.               }*/  
  177.               V_VT (&vtStart) = VT_EMPTY;  
  178.               spStartTime.Release();  
  179.               V_VT (&vtEnd) = VT_EMPTY;  
  180.               spEndTime.Release();                                                                                                                                   // 以下为指针释放,整个程序中最需要注意的部分  
  181.   
  182.                
  183.   
  184.               spPIValue.Release();  
  185.               spPIValues.Release();  
  186.               spPIPoint.Release();  
  187.               spSDKVersion.Release();  
  188.               spPISDK.Release();  
  189.   
  190.        }  
  191.        catch( _com_error Err )  
  192.        {  
  193.               std::cout << "Error: "  
  194.                         << Err.Description()  
  195.                         << " : "  
  196.                         << Err.Error()  
  197.                         << std::endl;  
  198.               return (1);  
  199.        }  
  200.        Sleep(5000);  
  201.        return 0;  
  202. }  

相关文章:

  • 解密回文——栈
  • Maven Docker部署
  • Django(六)模型(model)系统 -- 常用查询语法及进阶
  • Yii php 环境配置on ubuntu 12.04
  • 带动画的登录页面
  • JAVA中equal()和==的区别
  • 区块链软件公司:区块链技术能带来什么
  • Android:让WebView支持input type=”file”…元素
  • P3235 [HNOI2014]江南乐
  • ZYNQ的数据处理能力
  • Keras之父:比特币网络危机重重
  • 提高sql2005中带freetexttable和rank的全文检索的速度
  • js中if else switch 条件判断的替代方法
  • 菜根谭#142
  • 活在台积电的阴影下
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • android 一些 utils
  • canvas实际项目操作,包含:线条,圆形,扇形,图片绘制,图片圆角遮罩,矩形,弧形文字...
  • Java IO学习笔记一
  • Java基本数据类型之Number
  • Laravel核心解读--Facades
  • Python学习之路13-记分
  • Spring Boot MyBatis配置多种数据库
  • Spring核心 Bean的高级装配
  • 订阅Forge Viewer所有的事件
  • 开源地图数据可视化库——mapnik
  • 漂亮刷新控件-iOS
  • 七牛云假注销小指南
  • 区块链技术特点之去中心化特性
  • 如何在GitHub上创建个人博客
  • 树莓派 - 使用须知
  • 思维导图—你不知道的JavaScript中卷
  • 学习笔记TF060:图像语音结合,看图说话
  • 硬币翻转问题,区间操作
  • 云栖大讲堂Java基础入门(三)- 阿里巴巴Java开发手册介绍
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • Linux权限管理(week1_day5)--技术流ken
  • ​比特币大跌的 2 个原因
  • (附源码)spring boot基于小程序酒店疫情系统 毕业设计 091931
  • (附源码)springboot青少年公共卫生教育平台 毕业设计 643214
  • (深度全面解析)ChatGPT的重大更新给创业者带来了哪些红利机会
  • (转)一些感悟
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • .dat文件写入byte类型数组_用Python从Abaqus导出txt、dat数据
  • .NET CLR Hosting 简介
  • .net core 3.0 linux,.NET Core 3.0 的新增功能
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .NET core 自定义过滤器 Filter 实现webapi RestFul 统一接口数据返回格式
  • .NET gRPC 和RESTful简单对比
  • .net6+aspose.words导出word并转pdf
  • .NET大文件上传知识整理
  • .NET建议使用的大小写命名原则
  • [ element-ui:table ] 设置table中某些行数据禁止被选中,通过selectable 定义方法解决
  • [20170713] 无法访问SQL Server
  • [BZOJ] 2006: [NOI2010]超级钢琴