分享

网站安装打包

 悟静 2012-04-19

怎么制作一个网站安装的软件?

以前一开始的时候,是通过制作web安装程序,然后框的一下把网站安装完了。但是由于网站涉及到虚拟目录,创建网站等操作,直接制作web安装程序,如果中间有大量的配置是是灵活改变的,就变的相当的烦锁了。于是,换了一种方法:

通过制作一个网站安装的工具,然后通过制作应用程序安装程序:

--------就是制作一个网站安装工具,然后通过安装工具,再进行网站安装!!!

一.工具的组成:五个部分介绍:

1.软件环境检测

2.webconfig修改

3.新建网站(文件解压->创建网站->浏览网站)

4.IIS附加功能(查看站点、开启站点,停止站点、重启IIS、停止IIS、启动IIS)

5.工具App.config配置修改

 


二.工具的打包,制作应用程序安装程序。

1.打包环境软件(可选)

2.打包IIS软件(可选)

3.压缩站点

4.创建程序菜单和桌面菜单

这一节主要说安装!

1。操作系统

这个应该不用了,没系统也没法运行了!

2。IIS安装

这个是重点,最后面介绍!

3。framework安装

这个也不用了,工具安装时会先检测,如果没安装这工具也打不开了!

4。RAR安装

这个可以通过调用RAR的安装文件启动安装。

代码简单的就一句话:Process.Start(“RAR的安装软件路径”);

 

 


以下重点介绍IIS的自动安装

 

IIS的自动安装也很简单,先说下原理,再说下步骤:

原理:通过调用Sysocmgr.exe系统自带的工具安装,主要参数为:"/i:sysoc.inf /u:\"这里是iis.txt文件路径\""

这里的参数没有iis安装文件的路径,那路径是在哪里指定的?答:路径就在注册表里指定了

注册表路径为:Machine\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup

这下面有两个键:SourcePath和ServicePackSourcePath即为路径

所以,运行Sysocmgr.exe之前,

一.是要先配置好iis.txt文件,

二.是要先改注册表路径。

三.是运行Sysocmgr.exe

四.是把注册表改回去

 

主要步骤如下:

一:新建一个txt文件,把IIS要装的组件,按如下格式编写:

[Components]
iis_common = ON
iis_www = ON
iis_asp = ON
iis_inetmgr = ON
aspnet= ON

----------------保存成iis.txt即可。里面的组件其它组件名称,可以通过查看iis6.0的帮助文档找到!

二、三、四步,直接给出代码出下:

 

IIS安装

          
/// <summary>
          
/// 安装IIS
          
/// </summary>

          
/// <param name="installPath">iis386文件夹路径</param>
          
/// <param name="iisTxt">即存放安装组件的文本路径</param>
          
/// <param name="errMsg">返回的错误信息</param>
          
/// <returns></returns>
          public static bool Install(string installPath, string iisTxt,out string errMsg)
          {
              errMsg 
= ""
;
              RegistryKey key 
= Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Setup",true
);
              
if (key == null) { return false
; }
              
string sourcePath = Convert.ToString(key.GetValue("SourcePath"
));
              
string servicePackSourcePath = Convert.ToString(key.GetValue("ServicePackSourcePath"
));

              
try

              {

                  key.SetValue(
"ServicePackSourcePath", installPath);
                  key.SetValue(
"SourcePath"
, installPath);

                  Process rarPro 
= new
 Process();
                  rarPro.StartInfo.FileName 
= "Sysocmgr.exe"
;
                  rarPro.StartInfo.Arguments 
= string.Format("/i:sysoc.inf /u:\"{0}\""
, iisTxt);
                  rarPro.StartInfo.UseShellExecute 
= false
;
                  rarPro.StartInfo.CreateNoWindow 
= false
;
                  rarPro.StartInfo.WindowStyle 
=
 ProcessWindowStyle.Hidden;
                  rarPro.Start();
//开始  

                  rarPro.WaitForExit();//等待退出
                  rarPro.Dispose();
                  
return true
;
              }
              
catch (Exception err) { errMsg =
 err.Message; }
              
finally

              {
                  key.SetValue(
"ServicePackSourcePath", servicePackSourcePath);
                  key.SetValue(
"SourcePath"
, sourcePath);
              }
              
return false
;
          }

在net中,在System.Configuration.ConfigurationManager中,提供了几个静态方法,用来修改配置文件。

如:System.Configuration.Configuration config = System.Configuration.ConfigurationManager.OpenMachineConfiguration();

获得应用程序下的配置文件,之后再用config进行操作。

如果是在web中,那就是操作webconfig了!不过现在在winform中,就成了操作app.config了。

 


 

于是,我选择了还是以操作xml的方式来修改webconfig。

这里写了几个类,主要也是模仿config的操作方式。代码如下:

 

代码
using System;
using
 System.Collections.Generic;
using
 System.Text;
using
 System.Xml;
namespace
 IISHelper
{
    
public class
 WebConfigHelper : IDisposable
    {
        
private bool
 loadIsOK;
        
/// <summary>

        
/// 加载是否成功
        
/// </summary>

        public bool LoadIsOK
        {
            
get { return
 loadIsOK; }
            
set { loadIsOK =
 value; }
        }

        
private XmlDocument xDox = new
 XmlDocument();
        
private string configPath = string
.Empty;
        
public WebConfigHelper(string
 webconfigPath)
        {
            
try

            {
                xDox.Load(webconfigPath);
                configPath 
= webconfigPath;
                loadIsOK 
= true
;
            }
            
catch { loadIsOK = false
; }
            
        }
        
public
 WebConfigAppSetting AppSetting
        {
            
get

            {
                XmlNode xNode
=xDox.SelectSingleNode("//configuration/appSettings");
                
if(xNode==null
)
                {
                    
return null
;
                }
                
return new WebConfigAppSetting(ref
 xNode);
            }
        }
        
public
 WebConfigConnectionStrings ConnectionStrings
        {
            
get

            {
                XmlNode xNode 
= xDox.SelectSingleNode("//configuration/connectionStrings");
                
if (xNode == null
)
                {
                    
return null
;
                }
                
return new WebConfigConnectionStrings(ref
 xNode);
            }
        }
        
public bool
 Save()
        {
            
try

            {
                xDox.Save(configPath);
                
return true;
            }
            
catch
 { }
            
return false
;
        }
        
#region IDisposable 成员

        
public void Dispose()
        {
            xDox 
= null
;
        }
        
#endregion

    }
    
public abstract class WebConfigBase
    {
        
protected
 XmlNode node;
        
public  void Add(string key, string
 value){}
        
public abstract void Set(string key, string
 value);
        
public abstract string Get(string
 key);
        
public  void Remove(string key, string
 value){}
    }
    
public class
 WebConfigAppSetting : WebConfigBase
    {
        
internal  WebConfigAppSetting(ref
 XmlNode xNode)
        {
            node 
=
 xNode;
        }
        
public override void Set(string key, string
 value)
        {
            
foreach (XmlNode addNode in
 node.ChildNodes)
            {
                
if (addNode.Attributes != null && addNode.Attributes["key"].Value ==
 key)
                {
                    addNode.Attributes[
"value"].Value =
 value;
                    
break
;
                }
            }
        }
        
public override string Get(string
 key)
        {
            
foreach (XmlNode addNode in
 node.ChildNodes)
            {
                
if (addNode.Attributes != null && addNode.Attributes["key"].Value ==
 key)
                {
                  
return  addNode.Attributes["value"
].Value;
                }
            }
            
return ""
;
        }
    }
    
public class
 WebConfigConnectionStrings : WebConfigBase
    {
        
internal  WebConfigConnectionStrings(ref
 XmlNode xNode)
        {
            node 
=
 xNode;
        }
        
public override void Set(string key, string
 value)
        {
            
foreach (XmlNode addNode in
 node.ChildNodes)
            {
                
if (addNode.Attributes != null && addNode.Attributes["name"].Value ==
 key)
                {
                    addNode.Attributes[
"connectionString"].Value =
 value;
                    
break
;
                }
            }
        }
        
public override string Get(string
 key)
        {
            
foreach (XmlNode addNode in
 node.ChildNodes)
            {
                
if (addNode.Attributes != null && addNode.Attributes["name"].Value ==
 key)
                {
                    
return addNode.Attributes["connectionString"
].Value;
                }
            }
            
return ""
;
        }
    }
}

 

 下面看一下界面的操作方法:

 

界面操作方式
 WebConfigHelper allConfig = new WebConfigHelper(allConfigPath);
            
if (allConfig.LoadIsOK
)
            {
                WebConfigAppSetting app 
=
 allConfig.AppSetting;
                
if (app != null
)
                {
                  
  app.Set("MosFTPUserName"
, txtMosFtpUserName.Text);
                    app.Set(
"MosFTPPassword"
, txtMosFtpPassword.Text);
                    app.Set(
"ProvideFor"
, cbbProvideFor.Text);
                 
}
                WebConfigConnectionStrings connString 
=
 allConfig.ConnectionStrings;
                
if (connString != null
)
                {
                    connString.Set(
"Conn", txtConn.Text);

                }
                allConfig.Save();
                allConfig.Dispose();

               
 MessageBox.Show("配置文件修改成功!");
            }

 

 这里提示一下,web.config中,不要带名称空间,就是xmlns="xxxx一大堆的";

在新建网站之前,就是要把打包好的项目拷贝一份到IIS指定的路径上,同时,还要为个别目录设置相应的访问权限!

于是就产生了两件事:

1。拷贝-》[这里我是采用RAR打包,然后解压]

2。设置权限

如果是用拷贝方式,关于文件夹Copy,可以参考我的这篇文章:

文件夹复制操作(非递归循环遍历文件夹)

http://www.cnblogs.com/cyq1162/archive/2007/05/28/762294.html

 


 

为什么我没采用拷贝的方法,前提有两个,就是项目的文件夹有太多,在制作应用程序安装程序时,只能添加文件,而文件夹只能一个一个的新建,太麻烦!要不就要把项目文件放到其它工程里,那通过项目主输出来实现。我也不想放到新工程或集成到工具项目里,麻烦!

于是,我通过压缩项目文件,当然没有压缩web.config,因为web.config是要修改的,在压缩包里就改不了。所以最后的做法是解压RAR+文件拷贝web.config!

关于RAR解压,这里给出一段代码就算解决了:

 

RAR解压
public bool WARToFoler(string rarFromPath, string rarToPath)
        {
            Process rarPro 
= new
 Process();
            rarPro.StartInfo.FileName 
=
 AppConfig.SoftSetup_WinRARSystemPath;
            rarPro.StartInfo.Arguments 
= string.Format(" x  \"{0}\" \"{1}\" -o+ -r -ibck"
, rarFromPath, rarToPath);
            rarPro.StartInfo.UseShellExecute 
= false
;
            rarPro.StartInfo.RedirectStandardInput 
= true
;
            rarPro.StartInfo.RedirectStandardOutput 
= true
;
            rarPro.StartInfo.RedirectStandardError 
= true
;
            rarPro.StartInfo.CreateNoWindow 
= true
;
            rarPro.StartInfo.WindowStyle 
=
 ProcessWindowStyle.Hidden;
            rarPro.OutputDataReceived 
+= new
 System.Diagnostics.DataReceivedEventHandler(p_OutputDataReceived);
            rarPro.ErrorDataReceived 
+= new
 DataReceivedEventHandler(rarPro_ErrorDataReceived);
            rarPro.Start();
//解压开始  

            rarPro.BeginOutputReadLine();
            rarPro.BeginErrorReadLine();
            rarPro.WaitForExit();
            rarPro.Dispose();
            
return
 IsOK;
        }
        
void rarPro_ErrorDataReceived(object
 sender, DataReceivedEventArgs e)
        {
            
if (e.Data!=null && e.Data != ""
)
            {
                outMsg.Text 
+= "失败:" + e.Data + "\r\n"
;
                IsOK 
= false
;
            }
        }
        
void p_OutputDataReceived(object
 sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            
if (e.Data != null && e.Data != ""
)
            {
                outMsg.Text
+="成功:" + e.Data + "\r\n"
;
            }
        }

 

AppConfig.SoftSetup_WinRARSystemPath这个是就是安装的RAR.exe路径!

-ibck参数是让解压在后台运行,这样可以不用弹出个解压框!

前些天也写过一篇和RAR相关的文章:

记录下关于调用RAR解压缩的问题

http://www.cnblogs.com/cyq1162/archive/2010/01/13/1646678.html

OK,RAR解压就这么告一段落,接下来,我有一个App_Data目录,由于会往里面写生成的xml,所以为之添加一个可写权限!


 

设置权限的方式有三种,一种用net自带的封装类。另一种直接调用cacls.exe实现,还有一种就是网上下的调用Microsoft.win32的某种复杂方式。

以下就用第一种了。用net自带的类实现,非常的简单,三行代码:

 

设置权限
System.Security.AccessControl.DirectorySecurity fSec = new DirectorySecurity();
fSec.AddAccessRule(
new FileSystemAccessRule("everyone", FileSystemRights.FullControl, InheritanceFlags.ContainerInherit |
 InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow));
System.IO.Directory.SetAccessControl(path, fSec);

 

这里是添加了一个everyone用户,当然也可以换成aspnet用户,具体看安全性要求给了!后面就给出了所有权限。

具体关于权限的说明,多百google度或在vs下看按F1帮助文档就清楚了!

在IIS6.0的帮助文档中,对于创建IIS,提供了三种程序管理方法,一种是WMI,另一种是ADSI,还有一种是命令行方法。

这里,采用网上代码比较多的ADSI编程方式进行。

 

 


 

用C#进行ADSI编程,需要引用添加名称空间:System.DirectoryServices

主要操作类是:DirectoryEntry

操作的内容主要是xml节点:这点上,最好从IIS-》网站右键-》所有任务-》将配置保存到一个文件

保存后,查看一下生成的xml内容。看一下网站的节点是什么格式的,这对编程有点帮助。

以下进入代码阶段

 

创建网站
DirectoryEntry iisEntry = new DirectoryEntry("IIS://localhost/w3svc");//获得IIS节点
//
创建站点WebSiteID为整形,随便生成,不重复即可,可能引发的问题,看我之前的一篇文章:
//
C# 创建网站 无法启动与停止的问题
//http://www.cnblogs.com/cyq1162/archive/2010/01/09/1642919.html

DirectoryEntry site = (DirectoryEntry)iisEntry.Invoke("Create""IIsWebServer", WebSiteID);

site.Invoke(
"Put""ServerComment"
, WebSiteName);
site.Invoke(
"Put""KeyType""IIsWebServer"
);
ArrayList serverBindings 
= new
 ArrayList();
serverBindings.Add(WebSiteIP 
+ ":" + WebSitePort + ":" +
 WebSiteDomain);
if (WebSiteIP2 != "" && WebSitePort2 != ""
)
{
   serverBindings.Add(WebSiteIP2 
+ ":" + WebSitePort2 + ":" +
 WebSiteDomain);
}
site.Invoke(
"Put""ServerBindings", serverBindings.ToArray());//这里是绑定多个IP

site.Invoke("Put""ServerState"4);//4为停止,2为启动
site.Invoke("Put""FrontPageWeb"1);
site.Invoke(
"Put""DefaultDoc""index.html"
);
site.Invoke(
"Put""ServerAutoStart"0
);
site.Invoke(
"Put""AuthFlags"0
);
site.Invoke(
"Put""ScriptMaps", ScriptArray().ToArray());//这里是一大堆2.0的脚本

site.Invoke("Put""ServerSize"1);
site.Invoke(
"SetInfo"
);

 

 

创建完网站后,要创建默认根节点,代码如下:

 

创建根节点
//创建默认根节点目录
                    DirectoryEntry siteVDir = site.Children.Add("root""IISWebVirtualDir");
                    siteVDir.Properties[
"AppIsolated"][0= 2
;
                    siteVDir.Properties[
"Path"][0=
 WebSitePath;
                    siteVDir.Properties[
"AccessFlags"][0= 513
;
                    siteVDir.Properties[
"FrontPageWeb"][0= 1
;
                    siteVDir.Properties[
"AppRoot"][0= string.Format("/LM/W3SVC/{0}/Root"
, WebSiteID);
                    siteVDir.Properties[
"AppFriendlyName"][0=
 WebSiteName;
                    siteVDir.Properties[
"AuthFlags"][0= 0
;
                    siteVDir.Properties[
"AccessScript"][0= true
;
                    siteVDir.Properties[
"AccessSource"][0= true
;
                    siteVDir.Properties[
"DirBrowseFlags"][0= 1073741886
;
                    siteVDir.Properties[
"AuthNTLM"][0= true;//集成win身份验证

                    siteVDir.Properties["AuthAnonymous"][0= true;//集成win身份验证
                    siteVDir.Properties["UNCPassword"][0= "";
                    siteVDir.Properties[
"DefaultDoc"][0=
 WebSiteDefaultDoc;
                 
                    siteVDir.CommitChanges();
                    site.CommitChanges();

 

 关于属性及意思,除了可通过导出xml来查看之外,也可以看IIS帮助文档下的“参考->配置数据库参考属性"进行进一步了解!

打完,收工!

接上一节,网站安装打包 新建网站[四][创建网站] 中

这里提供一下创建虚拟目录的大体方法,虚拟目录是通过Root节点去创建的:

 

创建虚拟目录
public bool CreateWebVirtualDir(string virtualName, string virtualPath, string siteID, out string msg)
        {
            
try

            {
                msg 
= "";
                siteID 
= (string.IsNullOrEmpty(siteID) ?
 WebSiteID : siteID);
                tempEntry.Path 
= IISEntryPath + "/" + siteID + "/root";//这里是一个Root节点的DirectoryEntry

                DirectoryEntry siteVDir = tempEntry.Children.Add(virtualName, "IISWebVirtualDir");
                siteVDir.Invoke(
"AppCreate"true
);
                siteVDir.Properties[
"Path"][0=
 virtualPath;
                siteVDir.Properties[
"AccessFlags"][0= 513
;
                siteVDir.Properties[
"AppFriendlyName"][0=
 virtualName;
                siteVDir.Properties[
"AuthFlags"][0= 0
;
                siteVDir.Properties[
"AccessScript"][0= true
;
                siteVDir.Properties[
"AccessSource"][0= true
;
                siteVDir.Properties[
"AuthNTLM"][0= true;//集成win身份验证

                siteVDir.Properties["AuthAnonymous"][0= true;//集成win身份验证
                siteVDir.Properties["DefaultDoc"][0= WebSiteDefaultDoc;
                siteVDir.Invoke(
"AppCreate2"new object[1] { 2
 });
                tempEntry.CommitChanges();
                siteVDir.CommitChanges();
                
return true
;
            }
            
catch
 (Exception err)
            {
                msg 
=
 err.Message;
            }
            
return false
;
        }

 下面再给出一些常用的方法:

网站同名检测:

 

确认网站是否相同

        
private bool CheckSiteExists(out string msg)
        {
            msg 
= ""
;
            
foreach (DirectoryEntry child in
 iisEntry.Children)
            {
                
if (child.SchemaClassName == "IIsWebServer"
)
                {
                    
if (child.Properties["ServerComment"].Value != null
)
                    {
                        
if (child.Properties["ServerComment"].Value.ToString().ToLower() ==
 WebSiteName.ToLower())
                        {
                            msg 
= "站点名称已存在!"
;
                            
return true
;
                        }
                    }
                }
            }
            
return false
;
        }

 

 

 删除一个站点:

 

站点删除
public bool DeleteWebSiteByID(string siteID)
        {
            
try

            {
                siteID 
= (string.IsNullOrEmpty(siteID) ? WebSiteID : siteID);
                tempEntry.Path 
= IISEntryPath + "/" +
 siteID;
                iisEntry.Children.Remove(tempEntry);
                iisEntry.CommitChanges();
                
return true
;
            }
            
catch

            {

            }
            
return false;
        }

 

Start和Stop网站:

 

站点停止和启动
public bool StartWebSite(string siteID)
        {
            
try

            {
                siteID 
= (string.IsNullOrEmpty(siteID) ? WebSiteID : siteID);
                tempEntry.Path 
= IISEntryPath + "/" +
 siteID;
                tempEntry.Invoke(
"Start"new object
[] { });
                
return true
;
            }
            
catch

            { }
            
return false;
        }

        
public bool StopWebSite(string
 siteID)
        {
            
try

            {
                siteID 
= (string.IsNullOrEmpty(siteID) ? WebSiteID : siteID);
                tempEntry.Path 
= IISEntryPath + "/" +
 siteID;
                tempEntry.Invoke(
"Stop"new object
[] { });
                
return true
;
            }
            
catch
 { }
            
return false
;
        }

 

在创建完网站与虚拟目录,接下来就是要浏览网站了,于是,在界面上多加一个按钮,点击浏览是顺势而加了:

代码就一句:

 

Process.Start("iexplore.exe"string.Format("http://{0}",txtWebsiteIP.Text));

 

 //IP地址用System.Net.Dns.GetHostAddresses(Dns.GetHostName())[0].ToString()就可获取

 


以下附加一下IIS一些其它功能:

 

IIS重启:

 

IIS重启
  public static bool ReStart(out string msg)
        {
            
try

            {
                msg 
= "";
                ServiceController iis 
= new ServiceController("iisadmin"
);
                
if (iis.Status ==
 ServiceControllerStatus.Running)
                {
                    iis.Stop();
                }
                Process.Start(
"iisreset");//重启

                iis.Dispose();
                
return true
;
            }
            
catch
 (Exception err)
            {
                msg 
=
 err.Message;
            }
            
return false
;
        }

 

 IIS 开启:

IIS Start
 public static bool Start()
        {
            ServiceController iis 
= new ServiceController("iisadmin"
);
            
if (iis.Status ==
 ServiceControllerStatus.Stopped)
            {
                iis.Start();
            }
            iis.Dispose();
            
return true
;
        }

 

IIS 停止:

 

IIS Stop
public static bool Stop()
        {
            ServiceController iis 
= new ServiceController("iisadmin"
);
            
if (iis.Status ==
 ServiceControllerStatus.Running)
            {
                iis.Stop();
            }
            iis.Dispose();
            
return true
;
        }

 

注册asp.net:

 

aspnet 注册
 string aspnet_regiisPath=@"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe";
            
if (!
System.IO.File.Exists(aspnet_regiisPath))
            {
                aspnet_regiisPath 
= aspnet_regiisPath.Replace("C:""D:"
);
                
if (!
System.IO.File.Exists(aspnet_regiisPath))
                {
                    aspnet_regiisPath 
= aspnet_regiisPath.Replace("D:""E:"
);
                    
if (!
System.IO.File.Exists(aspnet_regiisPath))
                    {
                        MessageBox.Show(
"找不到Aspnet_regiis.exe的文件路径!"
);
                        
return
;
                    }
                }
            }
            Process.Start(aspnet_regiisPath, 
"-i");

在winform的安装工具中,少不免有一些配置文件要放到app.config去,于是修改也是成了一种需求!

无论是修改web.config还是app.config,普遍方式都有两种,用net自带封装的类,或是自定义xml操作。

可参考之前的一篇:网站安装打包 webconfig修改[三]

这里用的,还是以xml方式操作,比竟类都写了,就顺路用上了。

这里的操作方式和webconfig的差不多一个样:

 

修改app.config
 string appConfigPath = startPath + "/XXX.exe.config";
                WebConfigHelper appConfig 
= new
 WebConfigHelper(appConfigPath);
                
if
 (appConfig.LoadIsOK)
                {
                    WebConfigAppSetting appSetting 
=
 appConfig.AppSetting;
                    
if (appSetting != null
)
                    {
                      
                        appSetting.Set(
"SoftSetup_WinRARSystemPath"
, txtSoftSetup_WinRARSystemPath.Text);
                        appSetting.Set(
"SoftSetup_IISPath", txtSoftSetup_IISPath.Text.Replace(startPath, ""
));
                    }
                    
if
 (appConfig.Save())
                    {
                        ConfigurationManager.RefreshSection(
"appSettings"
);
                        MessageBox.Show(
"修改成功!"); return
;
                    }
                }
                MessageBox.Show(
"修改失败!");

 

 

这里最值得一提的一句是:ConfigurationManager.RefreshSection("appSettings");

修改完app.config时,虽然是修改了文件,但运行在内存中的app.config却还没有修改.

所以你改完文件,再取值,还是内存中的旧值,因此修改完后,需要重新加载一下。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多