<Window x:Class="Blogs.Tools.Gather.MultiGatherWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:FYJ.Winui.Controls;assembly=FYJ.Winui"
xmlns:util="clr-namespace:Blogs.Tools.Util"
Title="文章批量采集工具" Height="550" Width="880">
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"></Setter>
</Style>
<Style TargetType="c:TextBoxEx">
<Setter Property="Height" Value="24"></Setter>
</Style>
<Style TargetType="c:ComboBoxEx">
<Setter Property="Height" Value="24"></Setter>
</Style>
<Style TargetType="c:CheckBoxEx">
<Setter Property="VerticalAlignment" Value="Center"></Setter>
</Style>
<util:ReverseConverter x:Key="ReverseConverter1"></util:ReverseConverter>
</Window.Resources>
<DockPanel >
<Menu DockPanel.Dock="Top">
<MenuItem Header="文件(F)"></MenuItem>
<MenuItem Header="工具(T)">
<MenuItem Header="选项"></MenuItem>
</MenuItem>
<MenuItem Header="帮助(H)"></MenuItem>
</Menu>
<ToolBarPanel DockPanel.Dock="Top">
<ToolBar ToolBar.OverflowMode="Never">
<Menu>
<MenuItem Header="开始" Background="AliceBlue" x:Name="btnStart" Click="btnGather_Click"></MenuItem>
<MenuItem Header="结束" Background="Aqua" Margin="10,0,0,0" x:Name="btnStop" IsEnabled="{Binding ElementName=btnStart,Path=IsEnabled,Converter={StaticResource ReverseConverter1}}" Click="btnStop_Click"></MenuItem>
</Menu>
</ToolBar>
</ToolBarPanel>
<StackPanel DockPanel.Dock="Top" Margin="0,20,0,20" >
<WrapPanel>
<TextBlock Text="文章发布类"></TextBlock>
<c:ComboBoxEx Width="300" Margin="10,0,0,0" Height="24" x:Name="cbConfigName" SelectionChanged="cbConfigName_SelectionChanged" ></c:ComboBoxEx>
</WrapPanel>
<WrapPanel Margin="0,10,0,0">
<TextBlock Text="链接字符串" ></TextBlock>
<TextBox Width="610" Margin="10,0,0,0" Height="24" x:Name="txtConnectionString"></TextBox>
<Button Content="测试" Width="60" Height="24" Margin="10,0,0,0" x:Name="btnTest" Click="btnTest_Click"></Button>
</WrapPanel>
<WrapPanel Margin="0,10,0,0">
<TextBlock Text="采集规则" Width="60" ></TextBlock>
<c:ComboBoxEx Width="300" Margin="10,0,0,0" x:Name="comboRule"></c:ComboBoxEx>
<c:CheckBoxEx Content="下载远程图片" Margin="10,0,0,0" Name="checkBox_DownPic"></c:CheckBoxEx>
<c:CheckBoxEx Content="下载远程附件" Margin="10,0,0,0" Name="chk_DownAttachment"></c:CheckBoxEx>
<c:CheckBoxEx Content="是否递归" Margin="10,0,0,0" Name="chk_isRecursive"></c:CheckBoxEx>
</WrapPanel>
<WrapPanel Margin="0,10,0,0">
<TextBlock Text="URL" Width="60" Foreground="#FFF31818"></TextBlock>
<c:TextBoxEx x:Name="txtFromUrl" Width="610" Margin="10,0,0,0" Height="24"/>
</WrapPanel>
<WrapPanel Margin="0,10,0,0">
<TextBlock Text="板块/分类" Width="60"></TextBlock>
<c:ComboBoxEx Width="150" Margin="10,0,0,0" Name="comboBox_category" DropDownOpened="comboBox_category_DropDownOpened"></c:ComboBoxEx>
<TextBlock Text="发布用户ID" Margin="10,0,0,0" Width="60"></TextBlock>
<c:TextBoxEx Text="1" Width="150" Margin="10,0,0,0" Name="txtPostUserID"></c:TextBoxEx>
<TextBlock Text="发布用户名" Margin="10,0,0,0" Width="60"></TextBlock>
<c:TextBoxEx Text="admin" Width="150" Margin="10,0,0,0" Name="txtPostUserName"></c:TextBoxEx>
</WrapPanel>
<WrapPanel Margin="0,10,0,0">
<TextBlock Text="来源" Width="60"></TextBlock>
<c:TextBoxEx Width="150" Margin="10,0,0,0" Name="txtFromSource"></c:TextBoxEx>
<TextBlock Text="作者" Margin="10,0,0,0" Width="60"></TextBlock>
<c:TextBoxEx Text="" Width="150" Margin="10,0,0,0" Name="txtFromAuthor"></c:TextBoxEx>
<TextBlock Text="采集条数:" Width="60" Margin="10,0,0,0"></TextBlock>
<c:TextBoxEx Text="100" Width="150" Margin="10,0,0,0" Name="txtGatherCount"></c:TextBoxEx>
</WrapPanel>
</StackPanel>
<Grid DockPanel.Dock="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="150"></ColumnDefinition>
<ColumnDefinition Width="150"></ColumnDefinition>
</Grid.ColumnDefinitions>
<c:TextBlockEx Text="就绪" Name="txtState" Grid.Column="0"></c:TextBlockEx>
<!--附件上传进度-->
<c:ProgressBarEx Grid.Column="1" Name="attachmentProgress"></c:ProgressBarEx>
<c:ProgressBarEx Grid.Column="2" Margin="10,0,0,0" Name="progressState" ValueChanged="progressState_ValueChanged"></c:ProgressBarEx>
</Grid>
<c:LogListBox x:Name="logList1"></c:LogListBox>
</DockPanel>
<Window.TaskbarItemInfo>
<TaskbarItemInfo x:Name="taskbarItemInfo1">
</TaskbarItemInfo>
</Window.TaskbarItemInfo>
</Window>
配置
<?xml version="1.0" encoding="utf-8" ?> <GatherTool> <Gathers> <Gather id="DisThread" type="Blogs.Tools.Gather.Discuz.DisThread,Blogs.Tools" providerName="MySql.Data.MySqlClient" connectionString="Server=127.0.0.1;Uid=root;Pwd=root;Database=db_ztku;Port=3306;CharSet=utf8;AutoEnlist=false" tablePre="ztku_"></Gather> </Gathers> <Analyzes> <Analyze type="Blogs.Tools.Gather.Analyze.CnblogsAnalyze,Blogs.Tools" pattern="www.cnblogs.com"></Analyze> <Analyze type="Blogs.Tools.Gather.Analyze.DiscuzAnalyze,Blogs.Tools"></Analyze> <Analyze type="Blogs.Tools.Gather.Analyze.IT168Analyze,Blogs.Tools" pattern="it168.com"></Analyze> </Analyzes> <Rules> <Rule name="Cnblogs" matchUrl="http://www.cnblogs.com"> <ArticlePatterns> <ArticlePattern>http://www.cnblogs.com/\w+/p/\d+.html</ArticlePattern> <ArticlePattern>http://www.cnblogs.com/\w+/archive/\d{4}/\d{2}/\d{2}/.+.html</ArticlePattern> </ArticlePatterns> <!--内容分析类--> <Analyze type="Blogs.Tools.Gather.Analyze.CnblogsAnalyze,Blogs.Tools"> </Analyze> </Rule> <Rule name="Admin10000" matchUrl="http://wwww.admin10000.com"> <ArticlePatterns> <ArticlePattern>/\w+/\d+.html</ArticlePattern> </ArticlePatterns> <Analyze type="Blogs.Tools.Gather.Analyze.RegexAnalyze,Blogs.Tools"> <TitlePattern> <![CDATA[ <title.*?>[\s|\n]*(.*)-\s*WEB开发者[\s|\n]*</title> ]]> </TitlePattern> <AuthorPattern> <![CDATA[ <span class="source">来源:<a\s*href=".*?".+?>(.*?)</a> ]]> </AuthorPattern> <FromPattern> <![CDATA[ <span class="source">来源:<a\s*href="(.*?)".+?> ]]> </FromPattern> <ContentPattern> <![CDATA[ <div class="body textStyle">((.|\n)*?)</div> ]]> </ContentPattern> </Analyze> </Rule> <Rule name="Discuz"> <ArticlePatterns> <ArticlePattern>thread-\d+-\d+-\d+.html</ArticlePattern> </ArticlePatterns> <!--内容分析类--> <Analyze type="Blogs.Tools.Gather.Analyze.DiscuzAnalyze,Blogs.Tools"> </Analyze> </Rule> <Rule name="IT168" matchUrl="it168.com"> <ArticlePatterns> <ArticlePattern></ArticlePattern> </ArticlePatterns> <!--内容分析类--> <Analyze type="Blogs.Tools.Gather.Analyze.IT168Analyze,Blogs.Tools"> </Analyze> </Rule> </Rules> <!--自动分析--> <AutoAnalyzes> <AutoAnalyze Author="" FromSource="" Title="" Content=""></AutoAnalyze> </AutoAnalyzes> </GatherTool>
后台CS
using Blogs.Tools.Gather.Analyze; using Blogs.Tools.Gather.Blog; using Blogs.Tools.Gather.Common; using Blogs.Tools.Gather.Discuz; using Blogs.Tools.Gather.Wordpress; using FYJ.Upload; using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Net; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Shell; using System.Xml; using Blogs.Tool.Gather.Common; using System.Text.RegularExpressions; namespace Blogs.Tools.Gather { /// <summary> /// MultiGatherWindow.xaml 的交互逻辑 /// </summary> public partial class MultiGatherWindow : Window { public MultiGatherWindow() { InitializeComponent(); } #region 初始化 //同步对象 private object sync = new object(); private CancellationTokenSource cancelTokenSource = new CancellationTokenSource(); //线程数 private const int THREAD_COUNT = 10; //已采集数 private int gathered = 0; protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); LoadClassConfig(); LoadMuitiAnalyzeConfig(); this.AllowDrop = true; } private void LoadClassConfig() { List<object> list = new List<object>(); DataSet ds = new DataSet(); ds.ReadXml(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Gather.xml")); DataTable dt = ds.Tables["Gather"]; foreach (DataRow dr in dt.Rows) { FYJ.Data.IDbHelper db = FYJ.Data.DbFactory.CreateIDbHelper(dr["providerName"].ToString(), dr["connectionString"].ToString()); string type = dr["type"].ToString(); IGather gather = (IGather)Activator.CreateInstance(Type.GetType(type)); gather.Db = db; gather.TablePre = dr["tablePre"].ToString(); gather.ID = dr["id"].ToString(); object extend = dr["extend"]; gather.Extend = (extend == null ? null : extend.ToString()); list.Add(new { Text = dr["id"].ToString(), Value = gather }); } this.cbConfigName.ItemsSource = list; this.cbConfigName.SelectedValuePath = "Value"; this.cbConfigName.DisplayMemberPath = "Text"; this.cbConfigName.SelectedIndex = 0; } private void LoadMuitiAnalyzeConfig() { List<object> list = new List<object>(); XmlDocument doc = new XmlDocument(); doc.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Gather.xml")); foreach (XmlNode node in doc.GetElementsByTagName("Rule")) { GatherRule rule = new GatherRule(); List<string> patterns = new List<string>(); foreach (XmlNode n in node.SelectNodes("ArticlePatterns//ArticlePattern")) { patterns.Add(n.InnerText); } rule.ArticlePatterns = patterns; XmlNode analyzeNode = node.SelectSingleNode("Analyze"); string type = analyzeNode.Attributes["type"].InnerText; IAnalyze analyze = (IAnalyze)Activator.CreateInstance(Type.GetType(type)); if (analyzeNode.SelectSingleNode("TitlePattern") != null) { analyze.TitlePattern = analyzeNode.SelectSingleNode("TitlePattern").InnerText.Trim(); } if (analyzeNode.SelectSingleNode("AuthorPattern") != null) { analyze.AuthorPattern = analyzeNode.SelectSingleNode("AuthorPattern").InnerText.Trim(); } if (analyzeNode.SelectSingleNode("FromPattern") != null) { analyze.FromPattern = analyzeNode.SelectSingleNode("FromPattern").InnerText.Trim(); } if (analyzeNode.SelectSingleNode("ContentPattern") != null) { analyze.ContentPattern = analyzeNode.SelectSingleNode("ContentPattern").InnerText.Trim(); } rule.Analyze = analyze; list.Add(new { Text = node.Attributes["name"].Value, Value = rule }); } this.comboRule.ItemsSource = list; this.comboRule.SelectedValuePath = "Value"; this.comboRule.DisplayMemberPath = "Text"; this.comboRule.SelectedIndex = 0; } #endregion #region 其它操作 private void btnTest_Click(object sender, RoutedEventArgs e) { if (cbConfigName.Items.Count > 0) { IGather gather = this.cbConfigName.SelectedValue as IGather; FYJ.Data.IDbHelper db = gather.Db; if (db.TestCanConnectionOpen()) { MessageBox.Show("连接成功"); } else { MessageBox.Show("连接失败"); } } } private async void comboBox_category_DropDownOpened(object sender, EventArgs e) { if (!String.IsNullOrEmpty(this.cbConfigName.Text)) { try { this.txtState.Text = "正在获取板块/分类列表..."; CateDataSource data = new CateDataSource(); IGather gather = this.cbConfigName.SelectedValue as IGather; await Task.Factory.StartNew(() => { data = gather.GetCate(); }); this.comboBox_category.ItemsSource = data.DataSource.DefaultView; this.comboBox_category.DisplayMemberPath = data.DisplayMember; this.comboBox_category.SelectedValuePath = data.ValueMember; this.comboBox_category.SelectedIndex = 0; this.txtState.Text = "就绪"; } catch (Exception ex) { this.txtState.Text = "获取板块错误:" + ex.Message; } } } private void cbConfigName_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (cbConfigName.Items.Count > 0) { this.comboBox_category.ItemsSource = null; IGather gather = this.cbConfigName.SelectedValue as IGather; FYJ.Data.IDbHelper db = gather.Db; this.txtConnectionString.Text = db.ConnectionString; } } #endregion #region 批量采集 private async void btnGather_Click(object sender, RoutedEventArgs e) { if (this.txtFromUrl.Text.Trim() == "") { MessageBox.Show("请输入url"); return; } if (this.comboBox_category.SelectedIndex == -1) { MessageBox.Show("请选择分类"); return; } this.btnStart.IsEnabled = false; this.txtState.Text = "正在分析Url..."; IGather gather = this.cbConfigName.SelectedValue as IGather; GatherRule rule = this.comboRule.SelectedValue as GatherRule; int count = Convert.ToInt32(this.txtGatherCount.Text); this.progressState.Visibility = Visibility.Visible; string url = this.txtFromUrl.Text.Trim(); this.gathered = 0; this.progressState.Value = 0; await Task.Factory.StartNew(() => { Gather(url, count, gather, rule); }); this.btnStart.IsEnabled = true; } #endregion private void ThreadWork(string url, IGather gather, GatherRule rule) { if (cancelTokenSource.IsCancellationRequested) { this.logList1.AddErrorItem("线程" + Thread.CurrentThread.ManagedThreadId + "操作已被用户取消"); return; } //采集 GatherSingle(Thread.CurrentThread.ManagedThreadId + "", url, gather, rule.Analyze); } private void Gather(string url, int count, IGather gather, GatherRule rule) { try { List<string> urls = rule.GetArticleUrls(url); if (this.chk_isRecursive.IsChecked == false) { this.progressState.Maximum = Math.Min(urls.Count, count); } else { this.progressState.Maximum = count; } count = (int)this.progressState.Maximum; for (int k = 0; k < urls.Count; ) { if (gathered < count) { List<Task> taskList = new List<Task>(); for (int i = 0; i < THREAD_COUNT && i < urls.Count && k < urls.Count; i++) { Task task = new Task(() => { Monitor.Enter(sync); if (k < urls.Count) { string u = urls[k]; k++; Monitor.Exit(sync); ThreadWork(u, gather, rule); } }, cancelTokenSource.Token); taskList.Add(task); task.Start(); gathered++; } this.txtState.Text = "正在等待线程完成..."; //等待所有线程完成 Task.WaitAll(taskList.ToArray()); } } for (int k = 0; k < urls.Count; k++) { //递归 if (gathered < count && this.chk_isRecursive.IsChecked == true) { if (urls[k] != url) { Gather(urls[k], count, gather, rule); } } } } catch (Exception ex) { this.logList1.AddErrorItem(ex.Message); if (!ex.Message.Contains("不能对已完成的任务调用 Start")) { //重试 this.logList1.AddItem("重试" + url + "..."); if (gathered < count) { Gather(url, count, gather, rule); } } } this.progressState.Value = this.progressState.Maximum; this.txtState.Text = "就绪"; } /// <summary> /// /// </summary> /// <param name="url"></param> /// <param name="gather"></param> /// <param name="ana"></param> private void GatherSingle(string threadid, string url, IGather gather, IAnalyze ana) { try { //Monitor.Enter(sync); //this.txtState.Text = "线程" + threadid + ":正在发布" + (this.progressState.Value + 1) + "/" + this.progressState.Maximum + "..."; //Monitor.Exit(sync); if (gather.IsExistFromUrl(url)) { this.logList1.AddErrorItem("线程" + threadid + ":已存在相同来源Url"); Step(); return; } ana.Url = url; ana.Html = FYJ.Common.HttpHelper.DoGet(url); Post model = new Post(); model.PostID = gather.GetNewID(); model.AuthorID = this.txtPostUserID.Text; if (this.comboBox_category.SelectedValue != null) { model.CateID = this.comboBox_category.SelectedValue.ToString(); } model.AuthorUserName = this.txtPostUserName.Text.Trim(); model.Title = ana.Title; model.Content = ana.Content; //暂时全部允许下载 model.AttachmentLimit = 0; model.FromAuthor = ana.Author; if (!String.IsNullOrEmpty(model.FromAuthor) && this.txtFromAuthor.Text.Trim() != "") { model.FromAuthor = this.txtFromAuthor.Text.Trim(); } model.FromSourceUrl = url; model.FromSource = ana.FromSource; if (!String.IsNullOrEmpty(model.FromSource) && this.txtFromSource.Text.Trim() != "") { model.FromSource = this.txtFromSource.Text.Trim(); } model.AttachmentCollection = null; model.PostBy = "MultiGather"; if (String.IsNullOrEmpty(model.Content)) { this.logList1.AddErrorItem("线程" + threadid + ":没有分析到" + url + "内容"); Step(); return; } List<Attachment> attachmentCollection = new List<Attachment>(); #region 获取html中的图片 if (this.checkBox_DownPic.IsChecked == true) { List<string> imageList = HttpAnalyze.GetTagAttribute(model.Content, "img", "src");//图片列表 for (int i = 0; i < imageList.Count; i++) { string imageRealPath = HttpAnalyze.GetRealPath(imageList[i], url); string fileName = System.IO.Path.GetFileName(imageRealPath.IndexOf("?") == -1 ? imageRealPath : imageRealPath.Substring(0, imageRealPath.IndexOf("?"))); this.logList1.AddItem("线程" + threadid + ":分析到远程图片:" + imageRealPath); Attachment att = new Attachment(); att.fileName = fileName; att.fileUrl = imageRealPath; att.isImage = true; attachmentCollection.Add(att); } } #endregion #region 获取html中的附件 if (this.chk_DownAttachment.IsChecked == true) { IEnumerable<Attachment> atts = ana.AttachmentCollection; if (atts != null && atts.Count() > 0) { string baseUrl = Regex.Match(url, "http://(.*?)/").Value; for (int i = 0; i < atts.Count(); i++) { string extension = System.IO.Path.GetExtension(atts.ElementAt(i).fileName).ToLower(); bool isImage = (extension == ".jpg" || extension == ".png" || extension == ".bmp" || extension == ".gif"); Attachment att = new Attachment(); att.fileName = atts.ElementAt(i).fileName; att.fileUrl = baseUrl + atts.ElementAt(i).fileUrl; att.isImage = isImage; att.objectTag = "attachment"; attachmentCollection.Add(att); } } } #endregion #region 上传附件和图片 FYJ.Upload.Util.FileSave save = new FYJ.Upload.Util.FileSave(); if (attachmentCollection.Count > 0) { this.attachmentProgress.Visibility = Visibility.Visible; this.attachmentProgress.Maximum = attachmentCollection.Count; for (int i = 0; i < attachmentCollection.Count; i++) { Attachment att = attachmentCollection[i]; Stream stream = null; UploadResult queryFromResult = null; if (att.fileUrl.StartsWith("http")) { queryFromResult = save.QueryFileByFromUrl(att.fileUrl).FirstOrDefault(); //如果已从该来源Url上下载过该文件 if (queryFromResult == null) { this.txtState.Text = "线程" + threadid + ":正在下载" + att.fileUrl; HttpWebResponse res = FYJ.Common.HttpHelper.GetResponse(att.fileUrl, "GET"); stream = res.GetResponseStream(); } } else { stream = new FileStream(att.fileUrl, FileMode.Open); } this.txtState.Text = "线程" + threadid + ":正在上传文件..."; save.ObjectID = model.PostID; save.ObjectTag = att.objectTag; if (gather is DisThread) { save.FileTag = "discuz"; save.ObjectType = "forum"; save.UploadDir = "forum"; } if (gather is DisArticle) { save.FileTag = "discuz"; save.ObjectType = "portal"; save.UploadDir = "portal"; } if (gather is BlogArticle) { save.FileTag = "blog"; save.ObjectType = "blog"; save.UploadDir = "blog"; } if (gather is WordArticle) { save.FileTag = "wordpress"; save.ObjectType = "wordpress"; save.UploadDir = "wordpress"; } FYJ.Upload.UploadResult upresult; if (stream != null) { upresult = save.Save(stream, System.IO.Path.GetFileName(att.fileUrl), att.fileUrl); } else { upresult = queryFromResult; //保存文件关系 save.SaveFileRelationDb(upresult.ID, model.PostID, null, att.objectTag); } att.fileID = upresult.ID; att.fileName = upresult.FileName; att.fileSize = upresult.Size; this.logList1.AddItem("线程" + threadid + ":成功下载文件并上传到服务器:" + att.fileUrl); //替换img标签的文件 model.Content = model.Content.Replace(att.fileUrl, upresult.Url); //必须在上句的下面 att.fileUrl = upresult.Url; this.attachmentProgress.Value = (i + 1); } } #endregion Thread.Sleep(3000); this.logList1.AddItem("线程" + threadid + ":采集测试完成..." + model.Title); //MessageEx message = gather.Insert(model); //if (message.Code > 0) //{ // this.logList1.AddItem("线程" + threadid + " " + message.Message); //} //else //{ // this.logList1.AddErrorItem("线程" + threadid + "从 " + url + " " + message.Message); //} } catch (Exception ex) { this.logList1.AddErrorItem("线程" + threadid + ":采集 " + url + " 发生异常:" + ex.Message); } Step(); } /// <summary> /// 进度条加1 /// </summary> private void Step() { Monitor.Enter(this.progressState); this.progressState.Value = this.progressState.Value + 1; Monitor.Exit(this.progressState); } private void progressState_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) { this.Dispatcher.Invoke((Action)(() => { if (this.progressState.Value == this.progressState.Maximum) { this.txtState.Text = "发布完成"; this.progressState.Value = this.progressState.Maximum; } else { this.txtState.Text = "正在发布..." + this.progressState.Value + "/" + this.progressState.Maximum; } this.taskbarItemInfo1.ProgressState = TaskbarItemProgressState.Normal; this.taskbarItemInfo1.ProgressValue = e.NewValue / this.progressState.Maximum; if (e.NewValue == this.progressState.Maximum) { this.taskbarItemInfo1.ProgressState = TaskbarItemProgressState.None; } })); } private void btnStop_Click(object sender, RoutedEventArgs e) { cancelTokenSource.Cancel(); this.btnStart.IsEnabled = true; } } }
5/16/2015 5:43:20 PM 118.193.198....
沙发