監(jiān)理公司管理系統(tǒng) | 工程企業(yè)管理系統(tǒng) | OA系統(tǒng) | ERP系統(tǒng) | 造價(jià)咨詢管理系統(tǒng) | 工程設(shè)計(jì)管理系統(tǒng) | 甲方項(xiàng)目管理系統(tǒng) | 簽約案例 | 客戶案例 | 在線試用
X 關(guān)閉

把.Net整合進(jìn)其他平臺(tái)

申請(qǐng)免費(fèi)試用、咨詢電話:400-8352-114

AMTeam.org

把.Net整合進(jìn)其他平臺(tái)

整合分布式應(yīng)用程序經(jīng)常是一件非常困難并且錯(cuò)綜復(fù)雜的任務(wù),即使是最富有經(jīng)驗(yàn)的開發(fā)者也可能會(huì)覺得頭疼。當(dāng)應(yīng)用程序在不同的操作系統(tǒng)以及涉及不同的程序平臺(tái)時(shí),這個(gè)集成問題變得尤其復(fù)雜。雖然說,Web服務(wù)承諾可以減輕程序員完成集成任務(wù)的困難程度,但是也可能給程序員們帶來一些意想不到的麻煩。在這里我們將把一個(gè)ASP.Net應(yīng)用程序和一個(gè)PHP Web服務(wù)連結(jié)起來,以學(xué)習(xí)一些整合分布式應(yīng)用程序的方法,以及必要的應(yīng)對(duì)措施,包括運(yùn)行什么以及不用去做什么。

這個(gè)Web服務(wù)在一個(gè)Apache服務(wù)器上運(yùn)行,并且使用PHP開發(fā)。它從各種微軟新聞組檢索新聞?wù)约八鼈兊年P(guān)聯(lián)的文本。即使由這個(gè)服務(wù)提供的數(shù)據(jù)可以直接使用內(nèi)部的.Net對(duì)象存取,但是這個(gè)服務(wù)還是將使用并提供一個(gè)連接到非.Net平臺(tái)上的不錯(cuò)的演示。我們這里要討論的實(shí)例基于.Net beta 2版。

創(chuàng)建一個(gè)Web服務(wù)代理

Visual Studio.NET提供了一個(gè)出色的機(jī)制用于自動(dòng)地生成可用于存取遠(yuǎn)程Web服務(wù)的代理對(duì)像。因此,要首先嘗試使用這些函數(shù)來導(dǎo)入由PHP服務(wù)提供的Web服務(wù)描述語言(Web Services Description Language,WSDL)文件。 還可以使用.Net SDK的WSDL.exe命令行公用程序。不幸的是,在使用VS.Net向?qū)?dǎo)入WSDL之后,并不能成功地創(chuàng)建一個(gè)代理。所以我必須把導(dǎo)入原始的WSDL文件后由VS.Net生成的文件轉(zhuǎn)換為WSDL:

1. 把模式域名空間從http://www.w3.org/1999/XMLSchema改成http://www.w3.org/2001/XMLSchema 然后清除所有的當(dāng)WSDL導(dǎo)入過程中由VS.Net添加的”q”域名空間。

2. 刪除 xmlns:tm=http://microsoft.com/wsdl/mime/textMatching/和xmlns: mime="http://schemas.xmlsoap.org/wsdl/mime/" 名字空間,因?yàn)檫@個(gè)應(yīng)用程序中不需要包含這些。

3. 刪除類型元素,因?yàn)樵嫉?WSDL文檔 并沒有包含Web服務(wù)的模式信息的指定的元素區(qū)段。

4. 改變輸入輸出元素消息屬性值為包含tns域名空間前綴的形式:

<portType name="nntpSoapPortType">
<o(jì)peration name="getheaders" parameterOrder="newsgroup numitems">
<input message="tns:getheaders" />
<o(jì)utput message="tns:getheadersresponse" />
</operation>
<o(jì)peration name="getarticle" parameterOrder="newsgroup article">
<input message="tns:getarticle" />
<o(jì)utput message="tns:getarticleresponse" />
</operation>
</portType>

在進(jìn)行了下面的這些微小的改變,VS.Net向?qū)軌蜃x取WSDL并且自動(dòng)地生成一個(gè)代理。在編譯了這個(gè)代理之后,它被包含在一個(gè)ASP.Net頁面中。然而,當(dāng)這個(gè)ASP.Net頁面被執(zhí)行:“ message does not have a correct SOAP root XML tag.”,這個(gè)錯(cuò)誤被當(dāng)作一個(gè)SOAP錯(cuò)誤從Web服務(wù)中返回。為了精確地評(píng)估這個(gè)錯(cuò)誤,代理調(diào)用被一個(gè)名為Proxy Trace的公用程序使用,以便代理生成SOAP包裝。這可以通過把下列代碼添加進(jìn)ASP.Net頁面來實(shí)現(xiàn):

msNews.Proxy = new System.Net.WebProxy( "http://localhost:8080");

在察看了由.Net代理生成的SOAP包裝之后,我有點(diǎn)奇怪為什么會(huì)返回這個(gè)錯(cuò)誤,因?yàn)閷?shí)際上一個(gè)相對(duì)的SOAP包裝被生成并被發(fā)送到Web服務(wù)。即使在嘗試了好幾個(gè)轉(zhuǎn)化成代理代碼之后這個(gè)錯(cuò)誤依然持續(xù)。代碼段列表2顯示了從PHP Web服務(wù)返回的完整的SOAP錯(cuò)誤包裝。

在使用VS.Net中創(chuàng)建的代理對(duì)象的好幾個(gè)把ASP.Net頁面與PHP Web服務(wù)連結(jié)的不成功的嘗試之后,我決定從頭開始創(chuàng)建SOAP包裝以便執(zhí)行更有效的程序調(diào)試。{起先,它看起來好像由.Net代理生成的模式域名空間可能是問題的關(guān)鍵,因?yàn)?Net使用2001模式規(guī)范而PHP服務(wù)使用的是1999版本的規(guī)范。

然而,我把自定義的SOAP包裝改為用1999版本代替2001版本,錯(cuò)誤依然存在。在嘗試了好幾個(gè)其他的小的改變之后,我決定把SOAP包裝使用的域名空間前綴和正文元素從soap (由.Net代理生成)改為SOAP - ENV,因?yàn)槲铱匆娫赟OAP錯(cuò)誤信息中返回了SOAP - ENV前綴。(見代碼2)這表面上看上去微不足道的改變竟解決了問題!當(dāng)處理任何請(qǐng)求的時(shí)候,PHP服務(wù)顯然需要SOAP - ENV前綴,而拒絕不包含SOAP - ENV前綴的要求。

創(chuàng)建一個(gè)自定義代理

既然已經(jīng)了解了為什么Web服務(wù)返回一個(gè)SOAP錯(cuò)誤,我們就可以創(chuàng)建一個(gè)自定義代理來生成網(wǎng)服務(wù)期待的SOAP包裝。雖然創(chuàng)建一個(gè)自定義SOAP包裝肯定比使用一個(gè)由VS.Net或者WSDL.exe公用程序生成的SOAP包裝要花更多的時(shí)間,但是這樣做可以完全控制包裝的內(nèi)容。為了開始創(chuàng)建自定義代理,我創(chuàng)建一個(gè)名為msnewsserviceproxy的包含兩個(gè)字段的新類:

public class MSNewsServiceProxy {
string _uri;
string _soapAction;
}

uri字段保存了Web服務(wù)的位置,而_soapAction字段保存了將要使用SOAP包裝發(fā)送的SOAPAction數(shù)據(jù)頭的名稱。在MSNewsServiceProxy類之內(nèi),添加CreateSoapEnvelope (),SendSoapEnvelope ()和FilterResult ()這三個(gè)方法。這些方法生成SOAP包裝請(qǐng)求,把它發(fā)送到Web服務(wù),然后過濾返回的SOAP包裝。讓我們逐一的看看每個(gè)方法。注意代碼在SOAP包裝的根元素上添加一個(gè)SOAP - ENV域名空間前綴。Web服務(wù)顯然需要這個(gè)特定的前綴,而拒絕任何不包含這個(gè)前綴的信息。因?yàn)閂S.Net生成的代理發(fā)送一個(gè)soap域名空間前綴(而不是SOAP - ENV),所以它的消息被拒絕。Web服務(wù)不應(yīng)該需要一個(gè)特定的域名空間前綴而為此拒絕不帶此前綴的消息,但是域名空間問題也是你必須注意要想使工作更好的完成,要執(zhí)行一些看上去不{0>可思議的事情。

在SOAP包裝被創(chuàng)建之后,SendSoapEnvelope ()方法(見代碼段4)使用了幾個(gè)System.Net和System.IO域名空間中的類來把這個(gè)包裝發(fā)送到Web服務(wù)中。代碼首先通過把_uri變量傳送到對(duì)象構(gòu)造器來創(chuàng)建一個(gè)HttpWebRequest對(duì)象。其次,與這個(gè)請(qǐng)求相關(guān)聯(lián)的相應(yīng)的Method,ContentType和Header都將被發(fā)送。然后一個(gè)StreamWriter對(duì)象和HttpWebRequest對(duì)象的請(qǐng)求流相關(guān)聯(lián),SOAP包裝就被使用StreamWriter的Write ()方法寫到流中。

從Web服務(wù)返回的SOAP包裝被HttpWebResponse對(duì)象的SendSoapEnvelope ()方法獲得。

HttpWebResponse response = (HttpWebResponse)request.GetResponse();

如果應(yīng)答不是空值,它將被載入一個(gè)XmlTextReader,XmlTextReader被用來填充XmlDocument對(duì)象。然后從這個(gè)方法中返回XmlDocument對(duì)象。

FilterSoapEnvelope ()方法分析SOAP應(yīng)答包裝并把從Web服務(wù)中返回的數(shù)據(jù)裝入自定義代理的“消費(fèi)者”使用的XmlDocument對(duì)象:

private XmlDocument
FilterSoapEnvelope(
XmlDocument doc) {
XmlDocument filterDoc =new XmlDocument();
XmlNode result = doc.SelectSingleNode("http://results");
XmlNode resultImport = filterDoc.ImportNode(result,true);
filterDoc.AppendChild(resultImport);
return filterDoc;
}

雖然過濾器可以使用好幾種方法執(zhí)行,但是FilterSoapEnvelope ()方法依靠XPath語句可以在應(yīng)答SOAP包裝中得到結(jié)果元素。

微軟新聞組PHP Web服務(wù)展示了允許取得新聞組新聞?wù)膬煞N方法:getheaders ()和getmessage ()。 你可以看到如何在自定義代理類中使用這兩種方法(見代碼段5)。 注意每個(gè)方法中的代碼傳遞Web服務(wù)方法名被調(diào)用到CreateSoapEnvelope ()方法和任何使用這個(gè)方法關(guān)聯(lián)的參數(shù)。 在SOAP包裝被發(fā)送以及應(yīng)答被接受之后,F(xiàn)ilterSoapEnvelope ()方法被調(diào)用來把返回的數(shù)據(jù)加載到一個(gè)XmlDocument對(duì)象中,同樣,這個(gè)對(duì)象也是代理“消費(fèi)者”使用的。

整合PHP Web服務(wù)

既然一個(gè)自定義代理類已經(jīng)準(zhǔn)備好被使用,那么把它整合進(jìn)一個(gè)ASP.Net頁面就變得很簡單了。getHeaders ()和getMessage ()方法可以調(diào)用Web服務(wù),存取返回的XmlDocument對(duì)象(見代碼段6和7) 在XmlDocument內(nèi)的子結(jié)點(diǎn)中的枚舉可以顯示這些數(shù)據(jù)。 雖然許多Web服務(wù)可以很容易地使用VS.Net或者WSDL.exe創(chuàng)建的代理類自動(dòng)地生成,但是仍然會(huì)有你需要?jiǎng)?chuàng)建自定義SOAP包裝來把.Net和其他的平臺(tái)整合起來的情況。 本文中介紹的內(nèi)容以及代碼就提供了完成這個(gè)整合工作的一種方法。

代碼段1:

POST http://www.codecraze.com/soap/nntp.php HTTP/1.1
User-Agent: Mozilla/4.0 (compatible;
MSIE 6.0; MS Web Services Client Protocol 1.0.2914.16)
Content-Type: text/xml; charset=utf-8
SOAPAction: "
http://www.codecraze.com/soap/nntp.php"
Content-Length: 683
Expect: 100-continue
Connection: Keep-Alive
Host:
www.codecraze.com

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap=
"
http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="
http://schemas.xmlsoap.org/soap/
encoding/"
xmlns:tns="
http://tempuri.org/"
xmlns:types="
http://tempuri.org/encodedTypes"
xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="
http://www.w3.org/2001/XMLSchema">
<soap:Body
soap:encodingStyle=
"
http://schemas.xmlsoap.org/soap/encoding/">
<q1:getheaders xmlns:q1=
"
http://www.codecraze.com/soap/nntp.xsd">
<newsgroup xsi:type="xsd:string">
microsoft.public.dotnet.xml
</newsgroup>
<numitems xsi:type="xsd:string">15</numitems>
</q1:getheaders>
</soap:Body>
</soap:Envelope>

代碼段2:

HTTP/1.1 200 OK
Date: Apr, 16 Dec 2002 15:57:47 GMT
Server: Apache/1.3.20 (Unix) ApacheJServ/1.1.2 PHP/
4.0.4pl1 FrontPage/5.0.2.2510 Rewrit/1.1a
X-Powered-By: PHP/4.0.4pl1
Content-Length: 419
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: text/xml; charset="utf-8"

<?xml version="1.0" encoding="utf-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="
http://schemas.xmlsoap.org/soap/
envelope/"
SOAP-ENV:encodingStyle="
http://schemas.xmlsoap.org/
soap/encoding/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Client.SOAPMessageFormat</
faultcode>
<faultstring>
message does not have a correct SOAP root XML tag
</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

代碼段3 :

private string CreateSoapEnvelope(
string method,string[] param1,
string[] param2) {
StringBuilder sb = new StringBuilder();
sb.Append("<SOAP-ENV:Envelope");
sb.Append(" xmlns:SOAP-" +
"ENV="
http://schemas.xmlsoap.org/soap/
envelope/"");
sb.Append(" xmlns:xsi=
"
http://www.w3.org/1999/XMLSchema-" +
instance"");
sb.Append(" xmlns:xsd=
"
http://www.w3.org/1999/XMLSchema">");
sb.Append("<SOAP-ENV:Body>");
sb.Append("<m:" + method + " xmlns:m="nntp.xsd">");
sb.Append("<" + param1[0] + ">" + param1[1] +
"</" + param1[0] + ">");
sb.Append("<" + param2[0] + ">" + param2[1] +
"</" + param2[0] + ">");
sb.Append("</m:" + method + ">");
sb.Append("</SOAP-ENV:Body>");
sb.Append("</SOAP-ENV:Envelope>");
return sb.ToString();
}

代碼段4:

private XmlDocument SendSoapEnvelope(string soapEnv) {
HttpWebRequest request =
(HttpWebRequest)WebRequest.Create(_uri);
XmlTextReader xmlReader = null;
XmlDocument xmlDoc = null;
request.Method = "POST";
request.ContentType = "text/xml";
request.Headers.Add("SOAPAction",_soapAction);
StreamWriter writer = new StreamWriter(
request.GetRequestStream());
writer.Write(soapEnv);
writer.Close();
HttpWebResponse response =
(HttpWebResponse)request.GetResponse();

if (response != null) {
xmlReader = new XmlTextReader(
response.GetResponseStream());
xmlDoc = new XmlDocument();
xmlDoc.Load(xmlReader);
return xmlDoc;
} else {
return xmlDoc;
}
}

代碼段5:

public XmlDocument getHeaders(string newsgroup,
string numitems) {
string soapEnv = CreateSoapEnvelope("getheaders",
new string[2]{"newsgroup",newsgroup},
new string[2]{"numitems",numitems});
return FilterSoapEnvelope(SendSoapEnvelope(soapEnv));
}

public XmlDocument getMessage(string newsgroup,
string article) {
string soapEnv = CreateSoapEnvelope("getmessage",
new string[2]{"newsgroup",newsgroup},
new string[2]{"article",article});
return FilterSoapEnvelope(SendSoapEnvelope(soapEnv));
}

代碼段6:

private void Page_Load(object sender, System.EventArgs e)
{
StringBuilder sb = new StringBuilder();
try {
MSNewsService.MSNewsServiceProxy news =
new MSNewsService.MSNewsServiceProxy();
XmlDocument newsHeaders =
news.getHeaders("microsoft.public.dotnet.xml",
"15");
sb.Append("<ul>");
foreach (XmlNode node in
newsHeaders.DocumentElement.ChildNodes) {
sb.Append("<li><a href="JavaScript:readArticle(
'" + node.FirstChild.InnerText + "','" +
node.ChildNodes.Item(1).InnerText +
"')">" + node.ChildNodes.Item(1).InnerText +
"</a></li>");
}
sb.Append("<ul>");
lblMessages.Text = sb.ToString();
}
catch (Exception exp) {
lblMessages.Text =
"The Web Service appears to be" + " unavailable";
}
}

代碼MSNewsServiceProxy.cs:

using System.Net;
using System.Xml;
using System.IO;
using System.Text;
namespace MSNewsService {

public class MSNewsServiceProxy {
string _uri;
string _soapAction;

public MSNewsServiceProxy() {
_uri = "
http://www.codecraze.com/soap/nntp.php";
_soapAction = "
http://www.codecraze.com/soap/nntp.php";
}

private XmlDocument SendSoapEnvelope(string soapEnv) {
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_uri);
XmlTextReader xmlReader = null;
XmlDocument xmlDoc = null;
request.Method = "POST";
request.ContentType = "text/xml";
request.Headers.Add("SOAPAction",_soapAction);
StreamWriter writer = new StreamWriter(request.GetRequestStream());
writer.Write(soapEnv);
writer.Close();
HttpWebResponse response = (HttpWebResponse)request.GetResponse();

if (response != null) {
xmlReader = new XmlTextReader(response.GetResponseStream());
xmlDoc = new XmlDocument();
xmlDoc.Load(xmlReader);
return xmlDoc;
} else {
return xmlDoc;
}
}
private string CreateSoapEnvelope(string method,string[] param1, string[] param2) {
StringBuilder sb = new StringBuilder();
sb.Append("<SOAP-ENV:Envelope");
sb.Append(" xmlns:SOAP-" +
"ENV="
http://schemas.xmlsoap.org/soap/envelope/"");
sb.Append(" xmlns:xsi="
http://www.w3.org/1999/XMLSchema-" +
"instance"");
sb.Append(" xmlns:xsd="
http://www.w3.org/1999/XMLSchema">");
sb.Append("<SOAP-ENV:Body>");
sb.Append("<m:" + method + " xmlns:m="nntp.xsd">");
sb.Append("<" + param1[0] + ">" + param1[1] + "</" + param1[0] + ">");
sb.Append("<" + param2[0] + ">" + param2[1] + "</" + param2[0] + ">");
sb.Append("</m:" + method + ">");
sb.Append("</SOAP-ENV:Body>");
sb.Append("</SOAP-ENV:Envelope>");
return sb.ToString();
}

public XmlDocument getHeaders(string newsgroup,string numitems) {
string soapEnv = CreateSoapEnvelope("getheaders",
new string[2]{"newsgroup",newsgroup},
new string[2]{"numitems",numitems});
return FilterSoapEnvelope(SendSoapEnvelope(soapEnv));
}
public XmlDocument getMessage(string newsgroup,string article) {
string soapEnv = CreateSoapEnvelope("getmessage",
new string[2]{"newsgroup",newsgroup},
new string[2]{"article",article});
return FilterSoapEnvelope(SendSoapEnvelope(soapEnv));
}

private XmlDocument FilterSoapEnvelope(XmlDocument doc) {
XmlDocument filterDoc = new XmlDocument();
XmlNode result = doc.SelectSingleNode("http://results");
XmlNode resultImport = filterDoc.ImportNode(result,true);
filterDoc.AppendChild(resultImport);
return filterDoc;
}

}
}

代碼msNewsClient.aspx.cs

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Xml;
using System.Text;

namespace MSNewsService
{

public class MSNewsServiceClient : System.Web.UI.Page
{
protected System.Web.UI.WebControls.Label lblMessages;

public MSNewsServiceClient()
{
Page.Init += new System.EventHandler(Page_Init);
}

private void Page_Load(object sender, System.EventArgs e) {
StringBuilder sb = new StringBuilder();
try {
MSNewsService.MSNewsServiceProxy news = new MSNewsService.MSNewsServiceProxy();
XmlDocument newsHeaders = news.getHeaders("microsoft.public.dotnet.xml","15");
sb.Append("<ul>");
foreach (XmlNode node in newsHeaders.DocumentElement.ChildNodes) {
sb.Append("<li><a href="JavaScript:readArticle('" + node.FirstChild.InnerText +
"','" + node.ChildNodes.Item(1).InnerText +
"')">" + node.ChildNodes.Item(1).InnerText + "</a></li>");
}
sb.Append("<ul>");
lblMessages.Text = sb.ToString();
}
catch (Exception exp) {
lblMessages.Text = "The Web Service appears to be unavailable";
}
}

private void Page_Init(object sender, EventArgs e)
{

InitializeComponent();
}


private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);

}

}
}

代碼msNewsMessage.aspx

<%@ Page language="c#" src="msNewsMessage.aspx.cs" Codebehind="msNewsMessage.aspx.cs" AutoEventWireup="false" Inherits="MSNewsService.msNewsMessage" %>
<HTML>
<body bgcolor="#ffffff">
<h2>
<asp:Label ID="lblMessageTitle" Runat="server" />
</h2>
<p>
<asp:Label ID="lblMessageText" Runat="server" />
</p>
</body>
</HTML>
代碼msNewsMessage.aspx.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Xml;

namespace MSNewsService
{

public class msNewsMessage : System.Web.UI.Page
{
protected System.Web.UI.WebControls.Label lblMessageTitle;
protected System.Web.UI.WebControls.Label lblMessageText;

public msNewsMessage()
{
Page.Init += new System.EventHandler(Page_Init);
}

private void Page_Load(object sender, System.EventArgs e) {
try {
MSNewsService.MSNewsServiceProxy news = new MSNewsService.MSNewsServiceProxy();
XmlDocument newsHeaders = news.getMessage("microsoft.public.dotnet.xml",
Request["articleID"]);
lblMessageTitle.Text = Request["title"];
lblMessageText.Text = newsHeaders.FirstChild.InnerText;
}
catch (Exception exp) {
lblMessageText.Text = "The Web Service appears to be unavailable";
}
}

private void Page_Init(object sender, EventArgs e)
{
InitializeComponent();
}


private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);

}

}
}

代碼msNewsClient.apsx

<%@ Page language="c#" src="msNewsClient.aspx.cs" Codebehind="msNewsClient.aspx.cs" AutoEventWireup="false" Inherits="MSNewsService.MSNewsServiceClient" %>
<HTML>
<head>
<script language="javascript">
function readArticle(id,title) {
openWindow("msNewsMessage.aspx?articleID=" + id +
"&title=" + title,500,400);
}
function openWindow(page,w,h) {
window.open(page,"","status=no,width=" + w +
",height=" + h + ",scrollbars=yes");
}
</script>
</head>
<body bgcolor="#ffffff">
<h2>Recent posts from: microsoft.public.dotnet.xml</h2>
<p />
<asp:Label ID="lblMessages" Runat="server" />
</body>
</HTML><0}

發(fā)布:2007-03-25 10:34    編輯:泛普軟件 · xiaona    [打印此頁]    [關(guān)閉]
相關(guān)文章:
上海OA系統(tǒng)
聯(lián)系方式

成都公司:成都市成華區(qū)建設(shè)南路160號(hào)1層9號(hào)

重慶公司:重慶市江北區(qū)紅旗河溝華創(chuàng)商務(wù)大廈18樓

咨詢:400-8352-114

加微信,免費(fèi)獲取試用系統(tǒng)

QQ在線咨詢