原文 Silverlight技术调查(4)——完成的调查结果

客户端使用Silverlight+DXperience,可以在线编辑各种常见文本及富文本文档(doc、docx、rtf、txt、html……),Silverlight使用了异步提交和响应(通过WebClient和WebRequest),提交时,自行编排HTTP请求格式(请参见相应RFC标准);编辑的数据流上传至Servlet后,Servlet使用commons-fileupload-1.2.2解包数据,获取附加的请求参数及附件内容。

程序效果:

下面为客户端和服务器端程序:

(一)客户端Silverlight由部分组成:

1、主程序画面Layout部分MainPage.xaml:

  1. <UserControl x:Class="RichEdit.MainPage"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. mc:Ignorable="d"
  7. d:DesignHeight="300" d:DesignWidth="400" xmlns:dxre="http://schemas.devexpress.com/winfx/2008/xaml/richedit" xmlns:dxb="http://schemas.devexpress.com/winfx/2008/xaml/bars" xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core" xmlns:dxr="http://schemas.devexpress.com/winfx/2008/xaml/ribbon">
  8. <UserControl.Resources>
  9. <ResourceDictionary>
  10. <dxre:RichEditUICommand x:Key="commands" />
  11. <dxre:RichEditStringIdConverter x:Key="stringIdConverter" />
  12. </ResourceDictionary>
  13. </UserControl.Resources>
  14. <Grid x:Name="LayoutRoot" Background="White">
  15. <dxb:BarManager Name="barManager1" ToolbarGlyphSize="Small">
  16. <dxb:BarManager.Items>
  17. <dxb:BarButtonItem Command="{Binding Path=FileNew, Mode=OneTime, Source={StaticResource commands}}" Name="biFileNew" />
  18. <dxb:BarButtonItem Command="{Binding Path=FileSaveAs, Mode=OneTime, Source={StaticResource commands}}" Name="biFileSaveAs" />
  19. <dxb:BarButtonItem Command="{Binding Path=FilePrint, Mode=OneTime, Source={StaticResource commands}}" Name="biFilePrint" />
  20. <dxb:BarButtonItem Command="{Binding Path=FilePrintPreview, Mode=OneTime, Source={StaticResource commands}}" Name="biFilePrintPreview" />
  21. <dxb:BarButtonItem Command="{Binding Path=FileBrowserPrint, Mode=OneTime, Source={StaticResource commands}}" Name="biFileBrowserPrint" />
  22. <dxb:BarButtonItem Command="{Binding Path=FileBrowserPrintPreview, Mode=OneTime, Source={StaticResource commands}}" Name="biFileBrowserPrintPreview" />
  23. <dxb:BarButtonItem Command="{Binding Path=EditUndo, Mode=OneTime, Source={StaticResource commands}}" Name="biEditUndo" />
  24. <dxb:BarButtonItem Command="{Binding Path=EditRedo, Mode=OneTime, Source={StaticResource commands}}" Name="biEditRedo" />
  25. <dxb:BarButtonItem Content="LoadFromWeb" Name="barButtonItemLoad" Glyph="/RichEdit;component/Images/Chrysanthemum.jpg" ItemClick="barButtonItemLoad_ItemClick" GlyphAlignment="Top" GlyphSize="Default" />
  26. <dxb:BarButtonItem Name="barButtonItemSave" Glyph="/RichEdit;component/Images/Desert.jpg" Content="SaveToWeb" ItemClick="barButtonItemSave_ItemClick" />
  27. <dxb:BarStaticItem Content="message..." Name="barStaticItemMsg" />
  28. </dxb:BarManager.Items>
  29. <dx:DockPanel>
  30. <dxr:RibbonControl dx:DockPanel.Dock="Top" Name="ribbonControl1">
  31. <dxr:RibbonControl.ApplicationMenu>
  32. <dxr:ApplicationMenuInfo />
  33. </dxr:RibbonControl.ApplicationMenu>
  34. <dxr:RibbonDefaultPageCategory>
  35. <dxr:RibbonPage Caption="{Binding Source={StaticResource stringIdConverter}, ConverterParameter=Caption_PageFile, Converter={StaticResource stringIdConverter}, Mode=OneTime}" Name="pageFile">
  36. <dxr:RibbonPageGroup Caption="{Binding Source={StaticResource stringIdConverter}, ConverterParameter=Caption_GroupCommon, Converter={StaticResource stringIdConverter}, Mode=OneTime}" Name="grpCommon" ShowCaptionButton="False">
  37. <dxb:BarButtonItemLink BarItemName="biFileNew" />
  38. <dxb:BarButtonItemLink BarItemName="biFileSaveAs" />
  39. <dxb:BarButtonItemLink BarItemName="biFilePrint" />
  40. <dxb:BarButtonItemLink BarItemName="biFilePrintPreview" />
  41. <dxb:BarButtonItemLink BarItemName="biFileBrowserPrint" />
  42. <dxb:BarButtonItemLink BarItemName="biFileBrowserPrintPreview" />
  43. <dxb:BarButtonItemLink BarItemName="biEditUndo" />
  44. <dxb:BarButtonItemLink BarItemName="biEditRedo" />
  45. </dxr:RibbonPageGroup>
  46. <dxr:RibbonPageGroup Caption="Ribbon Page Group 1" Name="ribbonPageGroup1">
  47. <dxb:BarButtonItemLink BarItemName="barButtonItemLoad" />
  48. <dxb:BarButtonItemLink BarItemName="barButtonItemSave" />
  49. </dxr:RibbonPageGroup>
  50. <dxr:RibbonPageGroup Caption="Ribbon Page Group 2" Name="ribbonPageGroup2">
  51. <dxb:BarStaticItemLink BarItemName="barStaticItemMsg" />
  52. </dxr:RibbonPageGroup>
  53. </dxr:RibbonPage>
  54. </dxr:RibbonDefaultPageCategory>
  55. </dxr:RibbonControl>
  56. <dxre:RichEditControl Name="richEditControl1" BarManager="{Binding ElementName=barManager1, Mode=OneTime}" Ribbon="{Binding ElementName=ribbonControl1, Mode=OneTime}" />
  57. </dx:DockPanel>
  58. </dxb:BarManager>
  59. </Grid>
  60. </UserControl>

2、主程序逻辑部分MainPage.xaml.cs:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Net;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Documents;
  8. using System.Windows.Input;
  9. using System.Windows.Media;
  10. using System.Windows.Media.Animation;
  11. using System.Windows.Shapes;
  12. using System.IO;
  13. using DevExpress.XtraRichEdit;
  14. using System.Threading;
  15. namespace RichEdit
  16. {
  17. public partial class MainPage : UserControl
  18. {
  19. public MainPage()
  20. {
  21. InitializeComponent();
  22. }
  23. String uri = "http://localhost:8888/upload/UploadServlet";
  24. private void barButtonItemLoad_ItemClick(object sender, DevExpress.Xpf.Bars.ItemClickEventArgs e)
  25. {
  26. MessageBox.Show("Load Begin");
  27. ServerStreamLoad ssl = new ServerStreamLoad();
  28. ssl.Rp = new ShowLoadResultDelegate(ShowLoadResult);
  29. ssl.Load(uri);
  30. }
  31. private void barButtonItemSave_ItemClick(object sender, DevExpress.Xpf.Bars.ItemClickEventArgs e)
  32. {
  33. MessageBox.Show("Save Begin");
  34. WebRequest request = WebRequest.Create(new Uri(uri));
  35. Dictionary<String, String> nvc = new Dictionary<String, String>();
  36. nvc.Add("name", "value");
  37. nvc.Add("name1", "value1");
  38. ServerStreamSave sss = new ServerStreamSave();
  39. sss.Rp = new ShowSaveResultDelegate(ShowSaveResult);
  40. Stream stream = new MemoryStream();
  41. this.richEditControl1.SaveDocument(stream, DocumentFormat.Rtf);
  42. sss.Save(request, nvc, stream);
  43. }
  44. // 处理Load结果
  45. public void ShowLoadResult(Stream stream)
  46. {
  47. String msg = "OK";
  48. if (this.barStaticItemMsg.CheckAccess())
  49. {
  50. this.barStaticItemMsg.Content = msg;
  51. }
  52. else
  53. {
  54. Dispatcher.BeginInvoke(() =>
  55. {
  56. this.barStaticItemMsg.Content = msg;
  57. });
  58. }
  59. using (stream)
  60. {
  61. if (this.richEditControl1.CheckAccess())
  62. {
  63. this.richEditControl1.LoadDocument(stream, DocumentFormat.Rtf);
  64. }
  65. else
  66. {
  67. Dispatcher.BeginInvoke(() =>
  68. {
  69. this.richEditControl1.LoadDocument(stream, DocumentFormat.Rtf);
  70. });
  71. }
  72. }
  73. }
  74. // 处理Save结果
  75. public void ShowSaveResult(String msg)
  76. {
  77. if (this.barStaticItemMsg.CheckAccess())
  78. {
  79. this.barStaticItemMsg.Content = msg;
  80. }
  81. else
  82. {
  83. Dispatcher.BeginInvoke(() =>
  84. {
  85. this.barStaticItemMsg.Content = msg;
  86. });
  87. }
  88. }
  89. }
  90. // 处理结果的委托
  91. public delegate void ShowLoadResultDelegate(Stream stream);
  92. public delegate void ShowSaveResultDelegate(String s);
  93. }

3、数据流异步下载部分ServerStreamLoad.cs:

  1. using System;
  2. using System.Net;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using System.Windows.Documents;
  6. using System.Windows.Ink;
  7. using System.Windows.Input;
  8. using System.Windows.Media;
  9. using System.Windows.Media.Animation;
  10. using System.Windows.Shapes;
  11. using System.IO;
  12. namespace RichEdit
  13. {
  14. public class ServerStreamLoad
  15. {
  16. ShowLoadResultDelegate rp;
  17. public ShowLoadResultDelegate Rp
  18. {
  19. get { return rp; }
  20. set { rp = value; }
  21. }
  22. public void Load(String uri)
  23. {
  24. WebClient webClient = new WebClient();
  25. webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(LoadCompletedCallBack);
  26. webClient.OpenReadAsync(new Uri(uri));
  27. }
  28. private void LoadCompletedCallBack(object sender, OpenReadCompletedEventArgs e)
  29. {
  30. using (Stream stream = e.Result)
  31. {
  32. // read stream here
  33. rp(stream);
  34. }
  35. }
  36. }
  37. }

4、数据流异步提交部分:

  1. using System;
  2. using System.Net;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using System.Windows.Documents;
  6. using System.Windows.Ink;
  7. using System.Windows.Input;
  8. using System.Windows.Media;
  9. using System.Windows.Media.Animation;
  10. using System.Windows.Shapes;
  11. using System.IO;
  12. using System.Collections.Generic;
  13. using System.Windows.Threading;
  14. namespace RichEdit
  15. {
  16. public class ServerStreamSave
  17. {
  18. static ShowSaveResultDelegate rp;
  19. public ShowSaveResultDelegate Rp
  20. {
  21. get { return rp; }
  22. set { rp = value; }
  23. }
  24. public void Save(WebRequest request, Dictionary<String, String> nvc, Stream stream)
  25. {
  26. request.Method = "POST";
  27. UploadState us = new UploadState();
  28. us.Req = request;
  29. us.Nvc = nvc;
  30. us.Stream = stream;
  31. request.BeginGetRequestStream(new AsyncCallback(SaveRequestReadyCallBack), us);
  32. }
  33. private void SaveRequestReadyCallBack(IAsyncResult asyncResult)
  34. {
  35. string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
  36. byte[] boundarybytes = System.Text.Encoding.UTF8.GetBytes("\r\n--" + boundary + "\r\n");
  37. WebRequest request = ((UploadState)(asyncResult.AsyncState)).Req as WebRequest;
  38. request.ContentType = "multipart/form-data; boundary=" + boundary;
  39. request.UseDefaultCredentials = true;
  40. Stream rs = request.EndGetRequestStream(asyncResult);
  41. // 1.write boundery and key-value pairs
  42. string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
  43. IDictionary<String, String> nvc = ((UploadState)(asyncResult.AsyncState)).Nvc as IDictionary<String, String>;
  44. foreach (string key in nvc.Keys)
  45. {
  46. rs.Write(boundarybytes, 0, boundarybytes.Length);
  47. string formitem = string.Format(formdataTemplate, key, nvc[key]);
  48. byte[] formitembytes = System.Text.Encoding.UTF8.GetBytes(formitem);
  49. rs.Write(formitembytes, 0, formitembytes.Length);
  50. }
  51. rs.Write(boundarybytes, 0, boundarybytes.Length);
  52. // 2.write head
  53. string file = "file";
  54. string paramName = "file";
  55. string contentType = "unknown";
  56. string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n";
  57. string header = string.Format(headerTemplate, paramName, file, contentType);
  58. byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header);
  59. rs.Write(headerbytes, 0, headerbytes.Length);
  60. // 3.write stream here,the stream will be closed by invoker
  61. using (Stream stream = ((UploadState)(asyncResult.AsyncState)).Stream as Stream)
  62. {
  63. stream.Position = 0;
  64. //byte[] buffer = System.Text.Encoding.UTF8.GetBytes("aaa我11");
  65. byte[] data = new byte[4096];
  66. int count = stream.Read(data, 0, 4096);
  67. while (count > 0)
  68. {
  69. rs.Write(data, 0, count);
  70. count = stream.Read(data, 0, 4096);
  71. }
  72. }
  73. //rs.Write(buffer, 0, buffer.Length);
  74. // 4.write trailer
  75. byte[] trailer = System.Text.Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
  76. rs.Write(trailer, 0, trailer.Length);
  77. rs.Flush();
  78. rs.Close();
  79. request.BeginGetResponse(new AsyncCallback(SaveResponseReadyCallBack), request);
  80. }
  81. private void SaveResponseReadyCallBack(IAsyncResult asyncResult)
  82. {
  83. WebRequest request = asyncResult.AsyncState as WebRequest;
  84. WebResponse response = request.EndGetResponse(asyncResult);
  85. using (Stream responseStream = response.GetResponseStream())
  86. {
  87. StreamReader reader = new StreamReader(responseStream);
  88. // check response result by return msg.
  89. String msg = reader.ReadToEnd();
  90. rp(msg);
  91. }
  92. }
  93. }
  94. }

5、异步请求提交,回调封装参数部分UploadState.cs:

  1. using System;
  2. using System.Net;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using System.Windows.Documents;
  6. using System.Windows.Ink;
  7. using System.Windows.Input;
  8. using System.Windows.Media;
  9. using System.Windows.Media.Animation;
  10. using System.Windows.Shapes;
  11. using System.Collections;
  12. using System.Collections.Generic;
  13. using System.IO;
  14. namespace RichEdit
  15. {
  16. public class UploadState
  17. {
  18. IDictionary<String, String> nvc = null;
  19. public IDictionary<String, String> Nvc
  20. {
  21. get { return nvc; }
  22. set { nvc = value; }
  23. }
  24. WebRequest req = null;
  25. public WebRequest Req
  26. {
  27. get { return req; }
  28. set { req = value; }
  29. }
  30. Stream stream;
  31. public Stream Stream
  32. {
  33. get { return stream; }
  34. set { stream = value; }
  35. }
  36. }
  37. }

(二)服务器端使用commons-fileupload-1.2.2.jar实现上传:

1、web.xml配置:

  1. <servlet>
  2. <display-name>UploadServlet</display-name>
  3. <servlet-name>UploadServlet</servlet-name>
  4. <servlet-class>com.liyj.upload.servlet.UploadServlet</servlet-class>
  5. </servlet>
  6. <servlet-mapping>
  7. <servlet-name>UploadServlet</servlet-name>
  8. <url-pattern>/UploadServlet</url-pattern>
  9. </servlet-mapping>

2、UploadServlet.java(取打包的参数和附件)

  1. package com.liyj.upload.servlet;
  2. import java.io.*;
  3. import java.util.List;
  4. import javax.servlet.*;
  5. import javax.servlet.http.*;
  6. import org.apache.commons.fileupload.FileItem;
  7. import org.apache.commons.fileupload.FileUploadException;
  8. import org.apache.commons.fileupload.disk.DiskFileItemFactory;
  9. import org.apache.commons.fileupload.servlet.ServletFileUpload;
  10. public class UploadServlet extends HttpServlet {
  11. public void init(ServletConfig config) throws ServletException {
  12. super.init(config);
  13. }
  14. public void doGet(HttpServletRequest request, HttpServletResponse response) {
  15. ServletOutputStream out = null;
  16. System.out.println("into doGet..");
  17. try {
  18. out = response.getOutputStream();
  19. FileInputStream fis = new FileInputStream("D:\\file");
  20. byte[] data = new byte[4096];
  21. int count = fis.read(data);
  22. while(count > 0) {
  23. out.write(data, 0, count);
  24. count = fis.read(data);
  25. }
  26. fis.close();
  27. out.flush();
  28. out.close();
  29. } catch (Exception e)
  30. {
  31. e.printStackTrace();
  32. }
  33. }
  34. public void doPost(HttpServletRequest request, HttpServletResponse response) {
  35. ServletOutputStream out = null;
  36. System.out.println("into doPost..");
  37. DiskFileItemFactory factory = new DiskFileItemFactory();
  38. String path = "D:\\";
  39. factory.setRepository(new File(path));
  40. factory.setSizeThreshold(1024 * 1024);
  41. ServletFileUpload upload = new ServletFileUpload(factory);
  42. try {
  43. out = response.getOutputStream();
  44. List<FileItem> list = upload.parseRequest(request);
  45. for (FileItem item : list) {
  46. if (item.isFormField()) {
  47. String name = item.getFieldName();
  48. String value = item.getString("gbk");
  49. out.println(name + ":" + value);
  50. } else {
  51. String name = item.getFieldName();
  52. String value = item.getName();
  53. int start = value.lastIndexOf("\\");
  54. String fileName = value.substring(start + 1);
  55. //request.setAttribute(name, fileName);
  56. OutputStream os = new FileOutputStream(new File(path, fileName));
  57. InputStream is = item.getInputStream();
  58. byte[] buffer = new byte[1024];
  59. int length = 0;
  60. int fileLen = 0;
  61. while ((length = is.read(buffer)) > 0) {
  62. os.write(buffer, 0, length);
  63. fileLen += length;
  64. }
  65. out.println("OK");
  66. os.close();
  67. is.close();
  68. }
  69. }
  70. } catch (Exception e) {
  71. try {
  72. out.println("NG");
  73. } catch (IOException e1) {
  74. // TODO Auto-generated catch block
  75. e1.printStackTrace();
  76. }
  77. e.printStackTrace();
  78. }
  79. }
  80. }

---------- THE END-----------

Silverlight技术调查(4)——完成的调查结果的更多相关文章

  1. Silverlight技术调查(3)——国际化

    原文 Silverlight技术调查(3)——国际化 网上有很多关于Silverlight国际化的说明,包括MSDN的示例,都没有强调一点,下面以红色标示,基础国际化知识请先参考MSDN相关章节,关键 ...

  2. Silverlight技术调查(2)——跨域访问

    原文 Silverlight技术调查(2)——跨域访问 此调查web容器采用的是Tomcat,若允许所有域访问,只需在webapps下的根应用ROOT中,加入配置文件:clientaccesspoli ...

  3. Silverlight技术调查(1)——Html向Silverlight传参

    原文 Silverlight技术调查(1)——Html向Silverlight传参 近几日项目研究一个很牛的富文档编辑器DXperience RichEdit组件,调查环境为Silverlight4. ...

  4. Python技术调查

    1. IDE 2. Local Debugging & Remote Debugging 3. Profiling

  5. .NET Web开发技术简单整理

    在最初学习一些编程语言.一些编程技术的时候,做的更多的是如何使用该技术,如何更好的使用该技术解决问题,而没有去关注它的相关性.关注它的理论支持,这种学习技术的方式是短平快.其实工作中有时候也是这样,公 ...

  6. CTO和技术副总裁应该如何分工?谁才是技术领导者?

    谁是初创公司的技术领导者,是CTO还是技术副总裁?任何在创业公司工作的人都知道,我们不应该去问这个问题.因为这两个是非常不同的角色,角色本身会随着创业公司的发展而变化,两者对于业务规模都很重要. 简单 ...

  7. 使用SilverLight开发区域地图分析模块

    本人最近接收开发一个代码模块,功能主要是在页面上显示安徽省市地图,并且在鼠标移动到地图某市区域时,显示当前区域的各类信息等,一开始准备用百度地图,高德地图等地图工具进行开发,最后发现都不适合进行此类开 ...

  8. Silverlight开发工具汇总

    随着Silverlight技术的逐步完善,Silverlight应用大批的涌现,近期的2010年冬季奥运会,Silverlight作为首选视频播放技术,为全球提供在线赛事实况. Silverlight ...

  9. 转载:.NET Web开发技术简单整理

    在最初学习一些编程语言.一些编程技术的时候,做的更多的是如何使用该技术,如何更好的使用该技术解决问题,而没有去关注它的相关性.关注它的理论支持,这种学习技术的方式是短平快.其实工作中有时候也是这样,公 ...

随机推荐

  1. Swift - 1 (常量、变量、字符串、数组、字典、元组、循环、枚举、函数)

    Swift 中导入类库使用import,不再使用<>,导入自定义不再使用"" import Foundation 1> 声明变量和常量 在Swift中使用 &qu ...

  2. 队列的定义与实现(C语言实现)

    小时候.我们做早操的时候或者军训的时候,都排成一列,有头有尾.如果你迟到了,仅仅能站到最后面一个.退场的时候.都是由第一个先走的.这就是队列雏形. 队列的定义 队列是一种特殊的线性表 队列仅在线性表的 ...

  3. ThinkPHP - 每个操作都检测用户是否登录

    TP提供了一个自动执行的函数_initialize(), 你创建一个公共控制器CommonAction.class.php文件. 定义了此方法,不能存在构造方法__construct() <?p ...

  4. iOS 判断有无网络连接

    众所周知,我们在开发APP时,涉及网络连接的时候,都会想着提前判断一下当前的网络连接状态,如果没有网络,就不再请求url,省去不必要的步骤,所以,这个如何判断?其实很简单. 前提:工程添加:Syste ...

  5. F - Free DIY Tour(动态规划,搜索也行)

    这道题可用动态规划也可以用搜索,下面都写一下 Description Weiwei is a software engineer of ShiningSoft. He has just excelle ...

  6. instanceof 变量是否属于某一类 class 的实例

    <?phpclass MyClass{} class NotMyClass{}$a = new MyClass;$b = new NotMyClass;var_dump($a instanceo ...

  7. 我的Python成长之路---第三天---Python基础(9)---2016年1月16日(雾霾)

    一.集合 set和dict类似,也是一组key的集合,但不存储value.由于key不能重复,所以,在set中,没有重复的key. 集合和我们数学中集合的概念是一样的,也有交集,并集,差集,对称差集等 ...

  8. ZOJ 3329 One Person Game 【概率DP,求期望】

    题意:有三个骰子,分别有k1,k2,k3个面. 每次掷骰子,如果三个面分别为a,b,c则分数置0,否则加上三个骰子的分数之和. 当分数大于n时结束.求游戏的期望步数.初始分数为0 设dp[i]表示达到 ...

  9. Uniconnection 连 mysql 有时会断线的

    你定义localfailover:=ture.断线后会自己接上  firedac没这种功能.只有unidac有

  10. PHP脚本实现凯撒加(解)密

    原文:PHP脚本实现凯撒加(解)密 今天在看某ctf时候遇到一题凯撒加密的题,然后看到write up里有这样一句 顿时感觉这题目有点坑啊,这要不写个脚本来跑要推到啥时候啊,于是又了本文: <? ...