Java实现分类文件拷贝

类别:Java 点击:0 评论:0 推荐:

在Java中对文件进行分类

 如果你是个Java程序员,也许你对Java的文件组织已经非常清楚,例如你知道你们项目组是如何把众多的Java文件进行分类,组成整个项目工程.通常你们可能会根据业务来分组.有时候,我们作为项目成员,就会把自己的Java文件根据分组来进行分类的.下面,给出个例子:
 某项目ProjectX要给某家名叫comX的商业公司做的信息系统,根据业务需要分为,a,b,c,d四组,采用典型的三层结构,其中,工程中的文件分为前台部分和后台部分.经过分析,该项目最后把文件组织成这样的结构:
 a组写的前台文件所在的包为com.comX.foreg.a,相对应的后台文件放在com.comX.backg.a中
 b组写的前台文件所在的包为com.comX.foreg.b,相对应的后台文件放在com.comX.backg.b中
 c组写的前台文件所在的包为com.comX.foreg.c,相对应的后台文件放在com.comX.backg.c中
 d组写的前台文件所在的包为com.comX.foreg.d,相对应的后台文件放在com.comX.backg.d中
 当然,这只是我的一种简化的模型,实际上的项目由于业务交叉,综合复杂度等等因素应该比这样的结构复杂的多.象上面这样的工程,最后所有的代码放在文件夹"D:\ProjectX\src\"下面,而下面的文件组织和Java包路径可能又不一样.
 现在,我描述一下,我要阐述的问题,当这个工程已经基本完成之后,各个小组都出现了修改,集成发布人员,给项目组开了一个ftp,ftp文件结构正好和原来的结构一样,a组的程序员修改了某个文件后,按照他所在的位置,重新上传.这样,通常会有人把代码传错了位置,最后编译的时候出现错误,对于及其复杂,各组有所交叉的组织来说,编译的时候查错,都很困难.

 现在要做的是,让修改后的文件全部都传到一个文件夹中,通过程序把这些文件分发到他们应该在的位置.

 好,如何来作?
 思路很简单:每个文件都有他们的包路径,既然我们已经有了完整的文件结构,我们就能知道每一个包路径所对应的文件组织中的路径,如有一个文件ClassX.java,我们通过读取他的包路径为com.comX.foreg.a.a1,a1为a组的一个用例,但是它实际所在的位置为:
d:\ProjectX\src\business_1\com\comX\foreg\a\a1\ClassX.java,我们另外一个文件CalssY.java,他的包为com.comX.foreg.a.a2,它是a组的第二个用例,但他的实际存放路径为:d:\ProjectX\src\business_2\com\comX\foreg\a\a2\ClassY.java,
我们就可以根据事先指定的src目录找到他的合适位置.
 我所作的是为本项目组的程序员上传文件提供方便,他们把要上传的文件上传到一个文件夹,然后通过这个程序拷贝到正确的位置即可.下面是我实现的一些模块,你可以根据自己的特殊情况来做修改或扩展.

0.处理这个问题需要设置一些类变量:
  private String filePath="D:\\Work\\upload";      //文件上传路径,直接指向你的ftp目录吧
  private String configeFile="packageConfig.cxh";  //路径配置文件,后面会专门讲到
  private String objPath="D:\\Work\\gdlt\\src";    //目标文件路径,你可以指定任意,但要和配置文件一致
  private PrintStream ps=null;                     //输出消息重定向,用于处理过程中的信息输出定向
  private ArrayList lines=null;                    //保存配置文件
  String[] heads=null;                             //保存每条配置的头
  String globalLine;                               //记录一条配置信息

1.传入一个Java文件,返回该Java文件的包路径,如上面的ClassX.java,返回com.comX.foreg.a
  /**
   * 读取一个java文件,返回他的package信息取出来
   * @param file 文件句柄
   * @return 返回包路径
   */
  private String readPac(File file){
    String pac=null;
    try{
      BufferedReader fr=new BufferedReader(new FileReader(file));     //读取器
      String line=fr.readLine();                           
      while(line!=null){                                              //逐行处理
        //处理当前行
        line.trim();
        if(line.startsWith("package")){                               //找到包信息了
          pac=line.substring(7,line.length()-1).trim();
          break;
        }
        line=fr.readLine();
      }
    } catch(IOException ie){
      pac=null;
      ie.printStackTrace();
    }
    return pac;
  }

2.根据上面返回的包路径,返回该文件应该在的实际位置,例如上面的ClassX.java,返回d:\ProjectX\src\business_2\com\comX\foreg\a\
  /**
   *
   * 通过package信息把该文件应该在的位置取出来
   * @param pack  包路径
   * @return  返回该文件应该在的路径 返回null表示识别失败,需要读入 比如sbzs
   * 并智能的更新配置文件
   */
  private String parsePac(String pack,File cfgFile){
    String path=null;
    String line=null;
    try{
      for(int i=0;i<heads.length;i++){
        line=(String)lines.get(i);
        if(line.indexOf(pack)>0){ //找到了
          return heads[i];
        }
      }
      //没有找到,进行询问
      ps.print("请输入包 "+pack+" 所在的路径:(例如:sbzs)");
      DataInputStream dis=new DataInputStream(System.in);
      path=dis.readLine().trim().toLowerCase();
      dis.close();
      if(path==null||path.length()==0){
        ps.println("没有输入有效的包所在位置");
      } else{ //得到名字,写入
        int i;
        for(i=0;i<heads.length;i++){
          if(path.equals(heads[i])){ //找到了目标,把该包加入
            String tmp=(String)lines.get(i)+"|"+pack;
            lines.set(i,tmp); //更新
            break;
          }
        }
        if(i==heads.length){ //目前还没有这个位置,加入并更新
          lines.add(path+":"+pack);
          String[] tmp=new String[heads.length+1];
          System.arraycopy(heads,0,tmp,0,heads.length);
          tmp[heads.length]=path;
          heads=tmp;
        }
      }
    } catch(IOException ie){
      path=null;
      ie.printStackTrace();
    }
    return path;
  }

3.根据上面返回的实际位置,就可以把该文件拷贝到适合的位置.成功返回true
  /**
   * 把该文件拷贝到完整的路径中去
   * @param file 文件句柄
   * @param path 文件目标的绝对路径
   * @return
   */
  private boolean copy(File file,String fullPath){
    ps.println("开始拷贝文件....");
    ps.println("源文件:"+file.getName());
    ps.println("目标文件在:"+fullPath);
    String fileName=file.getName();
    int pos=fileName.lastIndexOf(File.separator);
    if(pos>0){
      fileName=fileName.substring(pos+1,fileName.length());
    }
    String lastPath=fullPath+File.separator+fileName;
    File objFile=new File(lastPath);
    if(objFile.exists()&&!objFile.delete()){ //如果存在则删除
      ps.println("删除目标文件失败");
      return false;
    }
    //开始拷贝
    try{
      objFile.createNewFile();
      FileInputStream fis=new FileInputStream(file);
      FileOutputStream fos=new FileOutputStream(objFile);
      byte[] buf=new byte[1024];
      int i=0;
      while((i=fis.read(buf))!=-1){
        fos.write(buf,0,i);
      }
      fis.close();
      fos.close();
    } catch(IOException ie){
      ie.printStackTrace();
      return false;
    }
    return true;
  }

到目前为止你可能都已经注意到了,这一切都非常简单,模块的思路也非常清楚.当然你肯定也注意到了,其中最重要的一个角色就是配置文件,如果没有这个,你可以看到在parsePac方法中,会要求你控制台输入某个文件对应的目标路径,这是对于新文件(即原来目标目录中没有的文件,新添的),对于对老文件的修改,我们都可以在配置文件中得到目标路径.下面我们看看配置文件如何得到.在这各之前,我们提到几个工具方法:
1.取文件名的扩展名,传入文件名,返回扩展名.
  /**
   *
   * @param fileName
   * @return
   */
  private String getExt(String fileName){
    int pos=fileName.lastIndexOf(".");
    if(pos>0){
      return fileName.substring(pos+1,fileName.length());
    }
    return null;
  }

2.判断一个目录是否有子目录,传入一个目录,判断有没有子目录,没则有返回true,有子目录返回false
  /**
   * 判断一个目录是否有子目录,没有返回true
   * @param dir
   * @return
   */
  private boolean hasNoSub(String directory){
    File dir=new File(directory);
    if(!dir.isDirectory()){           //当前不是目录,肯定没有子目录
      return true;
    }
    String[] subs=dir.list();        
    for(int i=0;i<subs.length;i++){   //对每个子元素进行判断,有目录就直接返回false
      File tmp=new File(dir+File.separator+subs[i]);
      if(tmp.isDirectory()){
        return false;
      }
    }
    return true;
  }


那么我们的配置文件记录什么,什么样的格式?假设我们指定我们的目标路径,类变量objPath=d:\ProjectX\src
那么我们的配置生成象这样:
business_1:com.comX.foreg.a.a1
business_2:com.comX.foreg.a.a2
如果a组的另外一个用例a3在business_1文件夹下,则配置文件的第一条配置信息为:
business_1:com.comX.foreg.a.a1|com.comX.foreg.a.a3
依次类推,通过文件的包信息与每条配置信息进行匹配,得知它所在的文件夹,当然如果objPath设置为d:\projectX\的话,那么配置信息的:前面部分为src/business_1.就这样,我们得到了可以使用的配置文件.下面我们看看配置文件相关的模块.
1.给定配置文件,把配置信息读入到类变量lines,它的每一个元素就是一条配置信息(配置文件的一行),同时把配置信息中:号前面的部分,我们称之为配置信息头(如business_1或src/business_1),取到我们的类变量heads中,它的作用就是为了提高处理效率,使得我们不用每次都配置信息中取头信息.
  /**
   * 读取配置文件,到ArrayList和heads
   * @return
   */
  private boolean readCfg(File cfgFile){
    try{
      BufferedReader fr=new BufferedReader(new FileReader(cfgFile));
      String line=fr.readLine();
      while(line!=null){
        //处理当前行
        line.trim();
        lines.add(line);
        line=fr.readLine();
      }
      int size=lines.size();
      heads=new String[size];
      for(int i=0;i<size;i++){
        line=(String)lines.get(i);
        heads[i]=getHead(line);
      }
      fr.close();
    } catch(IOException ie){
      ie.printStackTrace();
      return false;
    }
    return true;
  }

2.把一条配置信息的头解析出来,就是上面方法出现的getHead
  /**
   * 取得一条配置文件的头 例如business_1:
   * @param line 一条配置文件
   * @return
   */
  private String getHead(String line){
    int pos=line.indexOf(":");
    if(pos==-1){
      ps.println("配置文件有错");
      return "";
    }
    return line.substring(0,pos);
  }

3.该方法的作用就是,把我们的类变量lines的信息写入到配置文件中,因为可能有新添加的文件,用户输入了新的配置信息,或者在交互时,某些配置信息发生了变化.
  /**
   * 写入配置文件
   * @return
   */
  private boolean saveCfg(File cfgFile){
    if(lines==null||lines.size()==0){
      return false;
    }

    try{
      cfgFile.delete();
      cfgFile.createNewFile();
      BufferedWriter bw=new BufferedWriter(new FileWriter(cfgFile));
      int size=lines.size();
      for(int i=0;i<size;i++){
        bw.write((String)lines.get(i));
        bw.newLine();
      }
      bw.flush();
      bw.close();
    } catch(IOException ie){
      ie.printStackTrace();
      return false;
    }
    return true;
  }

上面的方法都是我们和配置文件的交互的一个过程,我们下一个问题时,如何初始化生成这个配置文件,基本上有下面几个模块:
1.这就是我们生成配置文件的根本,实际上就是到目标路径中收集结构信息的过程,其中可能调用一些工具方法.
  /**
   * 生成配置文件
   * @param pac 包路径
   * @param path 文件路径
   * @return 返回保存是否正确
   */
  public boolean createCfg(){
    File dir=new File(objPath);              //到目标路径中去收集信息
    String[] dirs=dir.list();               
    String curDir;
    char sep=File.separatorChar;
    String line;
    for(int i=0;i<dirs.length;i++){          //到每一个子元素(文件或目录)下工作
      if(!new File(objPath+sep+dirs[i]).isDirectory()){  //当前元素不是目录,做下一个
        continue;
      }
      globalLine=null;                              
      curDir=objPath+sep+dirs[i]+sep+"src";             
      getPath(curDir,null);                               //到当前目录下继续搜寻信息,getPath.
      line=dirs[i]+":"+globalLine;
      lines.add(line);
    }
    File cfgFile=new File(filePath+sep+configeFile);
    saveCfg(cfgFile);                                     //保存配置信息
    return true;
  }

其中curDir =...那一行是当前路径,其中的"src"因为我所在项目的子文件下还有一个src目录,但他不是包信息包含的,就像是我们的business_1下面为src/com/comX/...等等,所以就直接加上了.
明眼人一眼就能看出,关键方法是getPath,他是递归的,就像这样:

2.当前子目录下递归收集信息,对于每一条路径,我们只保存最长的,因为短的是可以在长的路径匹配得到的.
  /**
   * 递归收集
   * @param root    上一级目录
   * @param pack    当前已经收集到的信息
   */
  private void getPath(String root,String pack){
    if(hasNoSub(root)){                    //如果没有了子目录
      if(globalLine==null){
        globalLine=pack;
      } else{
        globalLine+="|"+pack;
      }
      return;
    }
    File dir=new File(root);
    char sep=File.separatorChar;
    String[] subs=dir.list();
    for(int i=0;i<subs.length;i++){
      String curPath=root+sep+subs[i];
      File tmp=new File(curPath);
      if(tmp.isDirectory()){
        if(pack==null){                   //如果是目录,则递归
          getPath(curPath,subs[i]);
        } else{
          getPath(curPath,pack+"."+subs[i]);
        }
      }
    }
  }

其中用到了类变量globalLine,它最终在调用完成的时候,会返回一条完整的配置信息.

好了,我就讲这么多了,不知道有没有讲明白,希望对你有所帮助,谢谢,最后一个方法是excute方法,就是执行这个完整的分类拷贝文件的工作.我把完整的类放在下面,就不另外写了.(这个类带有我本身项目的痕迹,不过不多,稍改即可)

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;

/**
 * <p>Title: </p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2004</p>
 * <p>Company: </p>
 * @author Caoxh
 * @version 1.0
 */

public class SaveFile{
  private String filePath="D:\\Work\\upload"; //文件上传路径
  private String configeFile="packageConfig.cxh"; //路径配置文件
  private String objPath="D:\\Work\\gdlt\\src"; //目标文件路径
  private PrintStream ps=null; //输出消息重定向
  private ArrayList lines=null; //保存配置文件
  String[] heads=null; //保存每条配置的头
  String globalLine; //记录一条配置信息
  /**
   * 读取一个java文件,返回他的package信息取出来
   * @param file 文件句柄
   * @return 返回包路径
   */
  private String readPac(File file){
    String pac=null;
    try{
      BufferedReader fr=new BufferedReader(new FileReader(file));
      String line=fr.readLine();
      while(line!=null){
        //处理当前行
        line.trim();
        if(line.startsWith("package")){ //找到包了
          pac=line.substring(7,line.length()-1).trim();
          break;
        }
        line=fr.readLine();
      }
    } catch(IOException ie){
      pac=null;
      ie.printStackTrace();
    }
    return pac;
  }

  /**
   * 取得一条配置文件的头 例如sbzs:
   * @param line 一条配置文件
   * @return
   */
  private String getHead(String line){
    int pos=line.indexOf(":");
    if(pos==-1){
      ps.println("配置文件有错");
      return "";
    }
    return line.substring(0,pos);
  }

  /**
   * 读取配置文件,到ArrayList和heads
   * @return
   */
  private boolean readCfg(File cfgFile){
    try{
      BufferedReader fr=new BufferedReader(new FileReader(cfgFile));
      String line=fr.readLine();
      while(line!=null){
        //处理当前行
        line.trim();
        lines.add(line);
        line=fr.readLine();
      }
      int size=lines.size();
      heads=new String[size];
      for(int i=0;i<size;i++){
        line=(String)lines.get(i);
        heads[i]=getHead(line);
      }
      fr.close();
    } catch(IOException ie){
      ie.printStackTrace();
      return false;
    }
    return true;
  }

  /**
   * 写入配置文件
   * @return
   */
  private boolean saveCfg(File cfgFile){
    if(lines==null||lines.size()==0){
      return false;
    }

    try{
      cfgFile.delete();
      cfgFile.createNewFile();
      BufferedWriter bw=new BufferedWriter(new FileWriter(cfgFile));
      int size=lines.size();
      for(int i=0;i<size;i++){
        bw.write((String)lines.get(i));
        bw.newLine();
      }
      bw.flush();
      bw.close();
    } catch(IOException ie){
      ie.printStackTrace();
      return false;
    }
    return true;
  }

  /**
   *
   * 通过package信息把该文件应该在的位置取出来
   * @param pack  包路径
   * @return  返回该文件应该在的路径 返回null表示识别失败,需要读入 比如sbzs
   * 并智能的更新配置文件
   */
  private String parsePac(String pack,File cfgFile){
    String path=null;
    String line=null;
    try{
      for(int i=0;i<heads.length;i++){
        line=(String)lines.get(i);
        if(line.indexOf(pack)>0){ //找到了
          return heads[i];
        }
      }
      //没有找到,进行询问
      ps.print("请输入包 "+pack+" 所在的路径:(例如:sbzs)");
      DataInputStream dis=new DataInputStream(System.in);
      path=dis.readLine().trim().toLowerCase();
      dis.close();
      if(path==null||path.length()==0){
        ps.println("没有输入有效的包所在位置");
      } else{ //得到名字,写入
        int i;
        for(i=0;i<heads.length;i++){
          if(path.equals(heads[i])){ //找到了目标,把该包加入
            String tmp=(String)lines.get(i)+"|"+pack;
            lines.set(i,tmp); //更新
            break;
          }
        }
        if(i==heads.length){ //目前还没有这个位置,加入并更新
          lines.add(path+":"+pack);
          String[] tmp=new String[heads.length+1];
          System.arraycopy(heads,0,tmp,0,heads.length);
          tmp[heads.length]=path;
          heads=tmp;
        }
      }
    } catch(IOException ie){
      path=null;
      ie.printStackTrace();
    }
    return path;
  }

  /**
   * 把该文件拷贝到完整的路径中去
   * @param file 文件句柄
   * @param path 文件目标的绝对路径
   * @return
   */
  private boolean copy(File file,String fullPath){
    ps.println("开始拷贝文件....");
    ps.println("源文件:"+file.getName());
    ps.println("目标文件在:"+fullPath);
    String fileName=file.getName();
    int pos=fileName.lastIndexOf(File.separator);
    if(pos>0){
      fileName=fileName.substring(pos+1,fileName.length());
    }
    String lastPath=fullPath+File.separator+fileName;
    File objFile=new File(lastPath);
    if(objFile.exists()&&!objFile.delete()){ //如果存在则删除
      ps.println("删除目标文件失败");
      return false;
    }
    //开始拷贝
    try{
      objFile.createNewFile();
      FileInputStream fis=new FileInputStream(file);
      FileOutputStream fos=new FileOutputStream(objFile);
      byte[] buf=new byte[1024];
      int i=0;
      while((i=fis.read(buf))!=-1){
        fos.write(buf,0,i);
      }
      fis.close();
      fos.close();
    } catch(IOException ie){
      ie.printStackTrace();
      return false;
    }
    return true;
  }

  /**
   * 生成配置文件
   * @param pac 包路径
   * @param path 文件路径
   * @return 返回保存是否正确
   */
  public boolean createCfg(){
    File dir=new File(objPath);
    String[] dirs=dir.list();
    String curDir;
    char sep=File.separatorChar;
    String line;
    for(int i=0;i<dirs.length;i++){
      if(!new File(objPath+sep+dirs[i]).isDirectory()){
        continue;
      }
      globalLine=null;
      curDir=objPath+sep+dirs[i]+sep+"src";
      getPath(curDir,null);
      line=dirs[i]+":"+globalLine;
      lines.add(line);
    }
    File cfgFile=new File(filePath+sep+configeFile);
    saveCfg(cfgFile);
    return true;
  }

  /**
   * 递归收集
   * @param root
   * @param pack
   */
  private void getPath(String root,String pack){
    if(hasNoSub(root)){ //如果没有了子目录
      if(globalLine==null){
        globalLine=pack;
      } else{
        globalLine+="|"+pack;
      }
      return;
    }
    File dir=new File(root);
    char sep=File.separatorChar;
    String[] subs=dir.list();
    for(int i=0;i<subs.length;i++){
      String curPath=root+sep+subs[i];
      File tmp=new File(curPath);
      if(tmp.isDirectory()){
        if(pack==null){ //如果是目录,则递归
          getPath(curPath,subs[i]);
        } else{
          getPath(curPath,pack+"."+subs[i]);
        }
      }
    }
  }

  /**
   * 判断一个目录是否有子目录,没有返回true
   * @param dir
   * @return
   */
  private boolean hasNoSub(String directory){
    File dir=new File(directory);
    if(!dir.isDirectory()){
      return true;
    }
    String[] subs=dir.list();
    for(int i=0;i<subs.length;i++){
      File tmp=new File(dir+File.separator+subs[i]);
      if(tmp.isDirectory()){
        return false;
      }
    }
    return true;
  }

  /**
   *
   * @param fileName
   * @return
   */
  private String getExt(String fileName){
    int pos=fileName.lastIndexOf(".");
    if(pos>0){
      return fileName.substring(pos+1,fileName.length());
    }
    return null;
  }

  /**
   * 循环处理一个文件夹
   */
  public void excute(){
    char sep=File.separatorChar;
    File list=new File(filePath+sep+"list.txt");
    File dir=new File(filePath);
    if(!dir.exists()||!dir.isDirectory()){ //指定的不是目录
      ps.println("指定上传文件的目录不正确");
      return;
    }
    File cfgFile=new File(filePath+sep+configeFile);
    try{
      PrintStream psList=new PrintStream(new FileOutputStream(list));
      if(!cfgFile.exists()&&!cfgFile.createNewFile()){ //配置文件不存在
        ps.println("指定的配置文件不存在");
        return;
      }
      if(!readCfg(cfgFile)){ //读入配置文件失败
        ps.println("读入配置文件失败");
        return;
      }
      File[] files=dir.listFiles();
      if(files.length<2){ //没有要上传的文件
        ps.println("指定的目录中没有文件");
        return;
      }
      //逐个文件处理
      for(int i=0;i<files.length;i++){
        File curFile=files[i];
        if(!curFile.isFile()||!getExt(curFile.getName()).equals("java")){
          continue;
        }
        psList.println(curFile.getName());
        //当前文件是java文件
        String pac=readPac(curFile); //取该文件的包路径
        ps.println("读包完成....");
        if(pac==null){
//          ps.println("文件读出包路径出错");
          return;
        }
        String path=parsePac(pac,cfgFile); //通过package取路径
        ps.println("解析完成....");
        if(path==null){
          ps.println("取文件的保存路径出错");
          continue; //跳过这个文件
        }
        //组装成一个完整的路径
        String fullPath=objPath+sep+path+sep+"src"+sep+pac.replace('.',sep);
        ps.println("建立拷贝路径:"+fullPath);
        if(!copy(curFile,fullPath)){
          ps.println("拷贝文件失败"+curFile.getName());
          continue;
        }
        curFile.delete();
        ps.println("成功保存一个文件");
      }
      ps.println("成功保存所有文件");
      if(!saveCfg(cfgFile)){
        ps.println("保存配置文件失败");
        return;
      }
    } catch(IOException ie){
      ie.printStackTrace();
    }
  }

  /**
   * 构造函数
   */
  public SaveFile(){
    try{
      File log=new File(filePath+File.separator+"cf.log");
      if(log.exists()){
        log.delete();
      }
      log.createNewFile();
      FileOutputStream fos=new FileOutputStream(log);
      ps=new PrintStream(fos);
    } catch(IOException ie){
      ie.printStackTrace();
      System.out.print("创建日志失败!");
      ps=System.out;
    }
    lines=new ArrayList();
  }

  public static void main(String[] args){
    SaveFile sf=new SaveFile();
    if(args.length==1){
      if(args[0].equals("mc")){         //生成配置文件
        sf.createCfg();
      }
      if(args[0].equals("cf")){         //完成分类拷贝工作
        sf.excute();
      }
    } else{
      sf.ps.println("请指定参数:\n mc 生成配置文件\n cf 拷贝文件");
      return;
    }
  }
}


曹想华 作于2005-1-15 13:11:29
欢迎来信交流.blog主页上有我的联系方式.

本文地址:http://com.8s8s.com/it/it11874.htm