开发者社区> 小麋鹿666> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

实现用C#和VB.NET调用Ghostscript的API,把Postscript文件转为PDF文件。

简介:
+关注继续查看
利用Ghostscript提供的gsapi_vb实现转postscript为PDF,进一步成功把VB.NET代码转为C#。
所用GhostScript为AFPL Ghostscript 8.53
附上GhostScript提供的VB.NET代码:

None.gif' Copyright (c) 2002 Dan Mount and Ghostgum Software Pty Ltd
None.gif'
None.gif'
 Permission is hereby granted, free of charge, to any person obtaining 
None.gif'
 a copy of this software and associated documentation files (the 
None.gif'
 "Software"), to deal in the Software without restriction, including
None.gif'
 without limitation the rights to use, copy, modify, merge, publish, 
None.gif'
 distribute, sublicense, and/or sell copies of the Software, and to
None.gif'
 permit persons to whom the Software is furnished to do so, subject to 
None.gif'
 the following conditions:
None.gif'
None.gif'
 The above copyright notice and this permission notice shall be
None.gif'
 included in all copies or substantial portions of the Software.
None.gif'
None.gif'
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
None.gif'
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
None.gif'
 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
None.gif'
 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
None.gif'
 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
None.gif'
 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
None.gif'
 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
None.gif'
 SOFTWARE.
None.gif

None.gif
None.gif
' This is an example of how to call the Ghostscript DLL from
None.gif'
 Visual Basic.NET.  There are two examples, one converts
None.gif'
 colorcir.ps to PDF, the other is like command line Ghostscript.
None.gif'
 The display device is not supported.
None.gif'
None.gif'
 This code is not compatible with VB6.  There is another
None.gif'
 example which does work with VB6.  Differences include:
None.gif'
 1. VB.NET uses GCHandle to get pointer
None.gif'
    VB6 uses StrPtr/VarPtr
None.gif'
 2. VB.NET Integer is 32bits, Long is 64bits
None.gif'
    VB6 Integer is 16bits, Long is 32bits
None.gif'
 3. VB.NET uses IntPtr for pointers
None.gif'
    VB6 uses Long for pointers
None.gif'
 4. VB.NET strings are always Unicode
None.gif'
    VB6 can create an ANSI string
None.gif'
 See the following URL for some VB6 / VB.NET details
None.gif'
  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvb600/html/vb6tovbdotnet.asp
None.gif

None.gif
Option Explicit On 
None.gif
None.gif
Imports System.Runtime.InteropServices
None.gif
ExpandedBlockStart.gifContractedBlock.gif
Module gsapiModule gsapi
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Public Declare Sub CopyMemory()Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal dest As IntPtr, ByVal source As IntPtr, ByVal bytes As Long)
InBlock.gif
InBlock.gif    
'------------------------------------------------
InBlock.gif
    'UDTs Start
InBlock.gif
    '------------------------------------------------
ExpandedSubBlockStart.gifContractedSubBlock.gif
    <StructLayout(LayoutKind.Sequential)> Public Structure GS_RevisionStructure GS_Revision
InBlock.gif        
Public strProduct As IntPtr
InBlock.gif        
Public strCopyright As IntPtr
InBlock.gif        
Public intRevision As Integer
InBlock.gif        
Public intRevisionDate As Integer
ExpandedSubBlockEnd.gif    
End Structure

InBlock.gif    
'------------------------------------------------
InBlock.gif
    'UDTs End
InBlock.gif
    '------------------------------------------------
InBlock.gif

InBlock.gif    
'------------------------------------------------
InBlock.gif
    'Callback Functions Start
InBlock.gif
    '------------------------------------------------
InBlock.gif
    'These are only required if you use gsapi_set_stdio
ExpandedSubBlockStart.gifContractedSubBlock.gif
    Public Delegate Function StdioCallBack()Function StdioCallBack(ByVal handle As IntPtr, ByVal strptr As IntPtr, ByVal count As IntegerAs Integer
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Public Function gsdll_stdin()Function gsdll_stdin(ByVal intGSInstanceHandle As IntPtr, ByVal strz As IntPtr, ByVal intBytes As IntegerAs Integer
InBlock.gif        
' This is dumb code that reads one byte at a time
InBlock.gif
        ' Ghostscript doesn't mind this, it is just very slow
InBlock.gif
        If intBytes = 0 Then
InBlock.gif            gsdll_stdin 
= 0
InBlock.gif        
Else
InBlock.gif            
Dim ich As Integer = Console.Read()
InBlock.gif            
If ich = -1 Then
InBlock.gif                gsdll_stdin 
= 0 ' EOF
InBlock.gif
            Else
InBlock.gif                
Dim bch As Byte = ich
InBlock.gif                
Dim gcByte As GCHandle = GCHandle.Alloc(bch, GCHandleType.Pinned)
InBlock.gif                
Dim ptrByte As IntPtr = gcByte.AddrOfPinnedObject()
InBlock.gif                CopyMemory(strz, ptrByte, 
1)
InBlock.gif                ptrByte 
= IntPtr.Zero
InBlock.gif                gcByte.Free()
InBlock.gif                gsdll_stdin 
= 1
InBlock.gif            
End If
InBlock.gif        
End If
ExpandedSubBlockEnd.gif    
End Function

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Public Function gsdll_stdout()Function gsdll_stdout(ByVal intGSInstanceHandle As IntPtr, ByVal strz As IntPtr, ByVal intBytes As IntegerAs Integer
InBlock.gif        
' If you can think of a more efficient method, please tell me!
InBlock.gif
        ' We need to convert from a byte buffer to a string
InBlock.gif
        ' First we create a byte array of the appropriate size
InBlock.gif
        Dim aByte(intBytes) As Byte
InBlock.gif        
' Then we get the address of the byte array
InBlock.gif
        Dim gcByte As GCHandle = GCHandle.Alloc(aByte, GCHandleType.Pinned)
InBlock.gif        
Dim ptrByte As IntPtr = gcByte.AddrOfPinnedObject()
InBlock.gif        
' Then we copy the buffer to the byte array
InBlock.gif
        CopyMemory(ptrByte, strz, intBytes)
InBlock.gif        
' Release the address locking
InBlock.gif
        ptrByte = IntPtr.Zero
InBlock.gif        gcByte.Free()
InBlock.gif        
' Then we copy the byte array to a string, character by character
InBlock.gif
        Dim str As String
InBlock.gif        
Dim i As Integer
InBlock.gif        
For i = 0 To intBytes - 1
InBlock.gif            
str = str + Chr(aByte(i))
InBlock.gif        
Next
InBlock.gif        
' Finally we output the message
InBlock.gif
        Console.Write(str)
InBlock.gif        gsdll_stdout 
= intBytes
ExpandedSubBlockEnd.gif    
End Function

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Public Function gsdll_stderr()Function gsdll_stderr(ByVal intGSInstanceHandle As IntPtr, ByVal strz As IntPtr, ByVal intBytes As IntegerAs Integer
InBlock.gif        gsdll_stderr 
= gsdll_stdout(intGSInstanceHandle, strz, intBytes)
ExpandedSubBlockEnd.gif    
End Function

InBlock.gif    
'------------------------------------------------
InBlock.gif
    'Callback Functions End
InBlock.gif
    '------------------------------------------------
InBlock.gif

InBlock.gif    
'------------------------------------------------
InBlock.gif
    'API Calls Start
InBlock.gif
    '------------------------------------------------
InBlock.gif
    'Win32 API
InBlock.gif
    'GhostScript API
InBlock.gif
    '    Public Declare Function gsapi_revision Lib "gsdll32.dll" (ByVal pGSRevisionInfo As IntPtr, ByVal intLen As Integer) As Integer
ExpandedSubBlockStart.gifContractedSubBlock.gif
    Public Declare Function gsapi_revision()Function gsapi_revision Lib "gsdll32.dll" (ByRef pGSRevisionInfo As GS_Revision, ByVal intLen As IntegerAs Integer
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Public Declare Function gsapi_new_instance()Function gsapi_new_instance Lib "gsdll32.dll" (ByRef lngGSInstance As IntPtr, ByVal lngCallerHandle As IntPtr) As Integer
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Public Declare Function gsapi_set_stdio()Function gsapi_set_stdio Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr, ByVal gsdll_stdin As StdioCallBack, ByVal gsdll_stdout As StdioCallBack, ByVal gsdll_stderr As StdioCallBack) As Integer
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Public Declare Sub gsapi_delete_instance()Sub gsapi_delete_instance Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Public Declare Function gsapi_init_with_args()Function gsapi_init_with_args Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr, ByVal lngArgumentCount As IntegerByVal lngArguments As IntPtr) As Integer
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Public Declare Function gsapi_run_file()Function gsapi_run_file Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr, ByVal strFileName As StringByVal intErrors As IntegerByVal intExitCode As IntegerAs Integer
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Public Declare Function gsapi_exit()Function gsapi_exit Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr) As Integer
InBlock.gif    
'------------------------------------------------
InBlock.gif
    'API Calls End
InBlock.gif
    '------------------------------------------------
InBlock.gif

InBlock.gif    
'------------------------------------------------
InBlock.gif
    'User Defined Functions Start
InBlock.gif
    '------------------------------------------------
ExpandedSubBlockStart.gifContractedSubBlock.gif
    Public Function StringToAnsiZ()Function StringToAnsiZ(ByVal str As StringAs Byte()
InBlock.gif        
' Convert a Unicode string to a null terminated Ansi string for Ghostscript.
InBlock.gif
        ' The result is stored in a byte array.  Later you will need to convert
InBlock.gif
        ' this byte array to a pointer with GCHandle.Alloc(XXXX, GCHandleType.Pinned)
InBlock.gif
        ' and GSHandle.AddrOfPinnedObject()
InBlock.gif
        Dim intElementCount As Integer
InBlock.gif        
Dim intCounter As Integer
InBlock.gif        
Dim aAnsi() As Byte
InBlock.gif        
Dim bChar As Byte
InBlock.gif        intElementCount 
= Len(str)
InBlock.gif        
ReDim aAnsi(intElementCount + 1)
InBlock.gif        
For intCounter = 0 To intElementCount - 1
InBlock.gif            bChar 
= Asc(Mid(str, intCounter + 11))
InBlock.gif            aAnsi(intCounter) 
= bChar
InBlock.gif        
Next intCounter
InBlock.gif        aAnsi(intElementCount) 
= 0
InBlock.gif        StringToAnsiZ 
= aAnsi
ExpandedSubBlockEnd.gif    
End Function

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Public Function AnsiZtoString()Function AnsiZtoString(ByVal strz As IntPtr) As String
InBlock.gif        
' We need to convert from a byte buffer to a string
InBlock.gif
        Dim byteCh(1As Byte
InBlock.gif        
Dim bOK As Boolean = True
InBlock.gif        
Dim gcbyteCh As GCHandle = GCHandle.Alloc(byteCh, GCHandleType.Pinned)
InBlock.gif        
Dim ptrByte As IntPtr = gcbyteCh.AddrOfPinnedObject()
InBlock.gif        
Dim j As Integer = 0
InBlock.gif        
Dim str As String
InBlock.gif        
While bOK
InBlock.gif            
' This is how to do pointer arithmetic!
InBlock.gif
            Dim sPtr As New IntPtr(strz.ToInt64() + j)
InBlock.gif            CopyMemory(ptrByte, sPtr, 
1)
InBlock.gif            
If byteCh(0= 0 Then
InBlock.gif                bOK 
= False
InBlock.gif            
Else
InBlock.gif                
str = str + Chr(byteCh(0))
InBlock.gif            
End If
InBlock.gif            j 
= j + 1
InBlock.gif        
End While
InBlock.gif        AnsiZtoString 
= str
ExpandedSubBlockEnd.gif    
End Function

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Public Function CheckRevision()Function CheckRevision(ByVal intRevision As IntegerAs Boolean
InBlock.gif        
' Check revision number of Ghostscript
InBlock.gif
        Dim intReturn As Integer
InBlock.gif        
Dim udtGSRevInfo As GS_Revision
InBlock.gif        
Dim gcRevision As GCHandle
InBlock.gif        gcRevision 
= GCHandle.Alloc(udtGSRevInfo, GCHandleType.Pinned)
InBlock.gif        intReturn 
= gsapi_revision(udtGSRevInfo, 16)
InBlock.gif        Console.WriteLine(
"Revision = " & udtGSRevInfo.intRevision)
InBlock.gif        Console.WriteLine(
"RevisionDate = " & udtGSRevInfo.intRevisionDate)
InBlock.gif        Console.WriteLine(
"Product = " & AnsiZtoString(udtGSRevInfo.strProduct))
InBlock.gif        Console.WriteLine(
"Copyright = " & AnsiZtoString(udtGSRevInfo.strCopyright))
InBlock.gif
InBlock.gif        
If udtGSRevInfo.intRevision = intRevision Then
InBlock.gif            CheckRevision 
= True
InBlock.gif        
Else
InBlock.gif            CheckRevision 
= False
InBlock.gif        
End If
InBlock.gif        gcRevision.Free()
ExpandedSubBlockEnd.gif    
End Function

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Public Function CallGS()Function CallGS(ByVal astrGSArgs() As StringAs Boolean
InBlock.gif        
Dim intReturn As Integer
InBlock.gif        
Dim intGSInstanceHandle As IntPtr
InBlock.gif        
Dim aAnsiArgs() As Object
InBlock.gif        
Dim aPtrArgs() As IntPtr
InBlock.gif        
Dim aGCHandle() As GCHandle
InBlock.gif        
Dim intCounter As Integer
InBlock.gif        
Dim intElementCount As Integer
InBlock.gif        
Dim iTemp As Integer
InBlock.gif        
Dim callerHandle As IntPtr
InBlock.gif        
Dim gchandleArgs As GCHandle
InBlock.gif        
Dim intptrArgs As IntPtr
InBlock.gif
InBlock.gif        
' Print out the revision details.
InBlock.gif
        ' If we want to insist on a particular version of Ghostscript
InBlock.gif
        ' we should check the return value of CheckRevision().
InBlock.gif
        CheckRevision(704)
InBlock.gif
InBlock.gif        
' Load Ghostscript and get the instance handle
InBlock.gif
        intReturn = gsapi_new_instance(intGSInstanceHandle, callerHandle)
InBlock.gif        
If (intReturn < 0Then
InBlock.gif            
Return (False)
InBlock.gif        
End If
InBlock.gif
InBlock.gif        
' Capture stdio
InBlock.gif
        Dim stdinCallback As StdioCallBack
InBlock.gif        stdinCallback 
= AddressOf gsdll_stdin
InBlock.gif        
Dim stdoutCallback As StdioCallBack
InBlock.gif        stdoutCallback 
= AddressOf gsdll_stdout
InBlock.gif        
Dim stderrCallback As StdioCallBack
InBlock.gif        stderrCallback 
= AddressOf gsdll_stderr
InBlock.gif        intReturn 
= gsapi_set_stdio(intGSInstanceHandle, stdinCallback, stdoutCallback, stderrCallback)
InBlock.gif
InBlock.gif        
If (intReturn >= 0Then
InBlock.gif            
' Convert the Unicode strings to null terminated ANSI byte arrays
InBlock.gif
            ' then get pointers to the byte arrays.
InBlock.gif
            intElementCount = UBound(astrGSArgs)
InBlock.gif            
ReDim aAnsiArgs(intElementCount)
InBlock.gif            
ReDim aPtrArgs(intElementCount)
InBlock.gif            
ReDim aGCHandle(intElementCount)
InBlock.gif            
For intCounter = 0 To intElementCount
InBlock.gif                aAnsiArgs(intCounter) 
= StringToAnsiZ(astrGSArgs(intCounter))
InBlock.gif                aGCHandle(intCounter) 
= GCHandle.Alloc(aAnsiArgs(intCounter), GCHandleType.Pinned)
InBlock.gif                aPtrArgs(intCounter) 
= aGCHandle(intCounter).AddrOfPinnedObject()
InBlock.gif            
Next
InBlock.gif            gchandleArgs 
= GCHandle.Alloc(aPtrArgs, GCHandleType.Pinned)
InBlock.gif            intptrArgs 
= gchandleArgs.AddrOfPinnedObject()
InBlock.gif            callerHandle 
= IntPtr.Zero
InBlock.gif
InBlock.gif            intReturn 
= gsapi_init_with_args(intGSInstanceHandle, intElementCount + 1, intptrArgs)
InBlock.gif
InBlock.gif            
' Release the pinned memory
InBlock.gif
            For intCounter = 0 To intElementCount
InBlock.gif                aGCHandle(intCounter).Free()
InBlock.gif            
Next
InBlock.gif            gchandleArgs.Free()
InBlock.gif
InBlock.gif            
' Stop the Ghostscript interpreter
InBlock.gif
            gsapi_exit(intGSInstanceHandle)
InBlock.gif        
End If
InBlock.gif
InBlock.gif        
' release the Ghostscript instance handle
InBlock.gif
        gsapi_delete_instance(intGSInstanceHandle)
InBlock.gif
InBlock.gif        
If (intReturn >= 0Then
InBlock.gif            CallGS 
= True
InBlock.gif        
Else
InBlock.gif            CallGS 
= False
InBlock.gif        
End If
InBlock.gif
ExpandedSubBlockEnd.gif    
End Function

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Private Function ConvertFile()Function ConvertFile() As Boolean
InBlock.gif        
Dim astrArgs(10As String
InBlock.gif        astrArgs(
0= "ps2pdf" 'The First Parameter is Ignored
InBlock.gif
        astrArgs(1= "-dNOPAUSE"
InBlock.gif        astrArgs(
2= "-dBATCH"
InBlock.gif        astrArgs(
3= "-dSAFER"
InBlock.gif        astrArgs(
4= "-r300"
InBlock.gif        astrArgs(
5= "-sDEVICE=pdfwrite"
InBlock.gif        astrArgs(
6= "-sOutputFile=c:\out.pdf"
InBlock.gif        astrArgs(
7= "-c"
InBlock.gif        astrArgs(
8= ".setpdfwrite"
InBlock.gif        astrArgs(
9= "-f"
InBlock.gif        astrArgs(
10= "c:\gs\gs7.04\examples\colorcir.ps"
InBlock.gif        
Return CallGS(astrArgs)
ExpandedSubBlockEnd.gif    
End Function

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
Private Function InteractiveGS()Function InteractiveGS() As Boolean
InBlock.gif        
Dim astrArgs(2As String
InBlock.gif        astrArgs(
0= "gs" 'The First Parameter is Ignored
InBlock.gif
        astrArgs(1= "-c"
InBlock.gif        astrArgs(
2= "systemdict /start get exec"
InBlock.gif        
Return CallGS(astrArgs)
ExpandedSubBlockEnd.gif    
End Function

InBlock.gif
InBlock.gif
InBlock.gif    
'------------------------------------------------
InBlock.gif
    'User Defined Functions End
InBlock.gif
    '------------------------------------------------
InBlock.gif

ExpandedSubBlockStart.gifContractedSubBlock.gif    
Sub Main()Sub Main()
InBlock.gif        ConvertFile()
InBlock.gif        
'InteractiveGS()
InBlock.gif
        MsgBox("Done")
ExpandedSubBlockEnd.gif    
End Sub

InBlock.gif
ExpandedBlockEnd.gif
End Module

 

 

本文转自RubyPdf 的中文博客博客园博客,原文链接:http://www.cnblogs.com/hardrock/archive/2006/03/01/340580.html,如需转载请自行联系原作http://www.cnblogs.com/hardrock/archive/2006/05/17/402654.html

 

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
API经济最主要是学会调用API
众所周知,阿里云提供了全方位、多层次的云计算产品体系。阿里巴巴技术委员会主席王坚博士说过,要让云计算像水电一样方便获得。为了开发者无缝衔接阿里云的众多服务,将它们集成到自己的应用系统,阿里云开放了API和用来访问API的工具SDK。
2286 0
利用Java编码测试CSRF令牌验证的Web API
前一篇拙文是利用了Jmeter来测试带有CSRF令牌验证的Web API;最近几天趁着项目不忙,练习了用编码的方式实现。 有了之前Jmeter脚本的基础,基本上难点也就在两个地方:获取CSRF令牌、Cookie的传递。
986 0
利用Jmeter测试CSRF令牌验证的Web API
事情的起因是最近收到的一批测试需求,要测试公司HR系统的接口性能。这个是需要测试的接口列表: 所有的接口请求,都基于登录验证成功,否则将无法获得正确的应答。 首先想到的是在浏览器上捕捉请求。打开Chrome浏览器,调出开发者工具栏,在地址栏输入登录模块的地址,访问登录页面:   输入账号和密码,录制登录过程;然后定位到开发工具的Network页面,找到登录的事务。
1553 0
Asp.Net Core Web Api图片上传(一)集成MongoDB存储实例教程
Asp.Net Core Web Api图片上传及MongoDB存储实例教程(一) 图片或者文件上传相信大家在开发中应该都会用到吧,有的时候还要对图片生成缩略图。那么如何在Asp.Net Core Web Api实现图片上传存储以及生成缩略图呢?今天我就使用MongoDB作为图片存储,然后使用SixLabors作为图片处理,通过一个Asp.
1240 0
从头编写 asp.net core 2.0 web api 基础框架 (2)
上一篇是: http://www.cnblogs.com/cgzl/p/7637250.html Github源码地址是: https://github.com/solenovex/Building-asp.
932 0
从头编写 asp.net core 2.0 web api 基础框架 (1)
工具: 1.Visual Studio 2017 V15.3.5+ 2.Postman (Chrome的App) 3.Chrome (最好是) 关于.net core或者.net core 2.
1168 0
asp.net web api 2.2 基础框架(带例子)
链接:https://github.com/solenovex/asp.net-web-api-2.2-starter-template 简介 这个是我自己编写的asp.net web api 2.2的基础框架,使用了Entity Framework 6.2(beta)作为ORM。
1062 0
+关注
文章
问答
文章排行榜
最热
最新
相关电子书
更多
阿里云 API 精选手册(Alibaba Cloud API Playbook)
立即下载
ACE 区域技术发展峰会:Flink Python Table API入门及实践
立即下载
Java Spring Boot开发实战系列课程【第15讲】:Spring Boot 2.0 API与Spring REST Docs实战
立即下载