百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

详细Android连接远程的MySQL数据库实例

sinye56 2024-10-01 20:10 3 浏览 0 评论

前言

第一次连接远程MySQL数据,在此留下笔记,针对遇到的总是也一并给出,在此说明,连接远程数据库在我没遇到的问题暂时无法给出,我尽量按写程的时间最全的给出代码。连接的方法是采用mysql-connector-java-5.1.30-bin.jar包进行连接。欢迎讨论。
说明:

  1. 以下记录按时间先后顺利给出,采用同样的环境应该可以重现。
  2. 代码部分也由顺序给出,如:某一个方法或者类后面的代码可能比前面贴出的全,也可能是增加了内容,也可能是解决了前面的某个问题。
  3. 为了程序的可扩展性,本示例采用数据与UI分离写法,自定义了页面的基类BasePager,initView()和initData()

环境配置

服务器:阿里云服务器 ECS
操作系统:centos 7
MySQL:version: 5.7.27
连接服务器软件:FinalShell
MySQL管理软件:RoboDB MySQL Manager
安卓开发软件:Androi Studio 3.5.2

查看MySQL:version方法

在登陆服务器后输入mysql -uroot -p再输入密码。

[root@njzhw ~]#  mysql -uroot -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1234354
Server version: 5.7.27 MySQL Community Server (GPL)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

新建Android工程

新建一个空白Activity的工程文,目前采用最新的各类文件版本,

build.gradle

app的build.gradle文件如下:所有支持均已最新版本。minSdkVersion 19因受我电脑外挂android模拟器的限制,采用了19。高了模拟器运行不起来。

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"
    defaultConfig {
        applicationId "cn.plczl.myapplication"
        minSdkVersion 19
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.13'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

activity_main.xml布局文件

MainActivity所对应的activity_main.xml的布局文只有一个FrameLayout,所有数据显示采用另外的代码实现。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <FrameLayout
        android:id="@+id/fl_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

MainActivity类修改为继承于Activity

新建好的MainActivity是继承于AppCompatActivity。区别是软件运行时不需要其默认的标题栏
百度的区别:
Activity 是其它 Activity 的基类,包括 AppCompatActivity。默认带标题栏,从字面理解,App兼容Activity,意思是允许我们轻易地将API 21+的特性应用到之前的那些老的,不兼容的Activity上面。例如很容易地将Toolbar添加到Activity上
代码如下:

package cn.plczl.mysql;
import android.app.Activity;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

新建一个基类

用于子页面来继承,这样可以有多个页面采用自定义的一类进行界面、数据分离处理。
在工程中新建一个base的包,包中建立一个BasePager的类。代码如下:本基类无需要继承,
主要代码如下:

  1. 其中有一个构造函数public BasePager(Context context),用于获取上下文和初始化视图(rootView);
  2. 一个抽象的initView方法,强制由调用的页面来实现,以实现不同页面有不同的UI结构;
  3. 一个初始化数据的方法(initData),本方法为一个空方法,由继承它的子页面来实现数据填充。在子页面中还可以在本方法中进行耗时的联网,数据请求,数据显示的操作。
    注意:
  4. 因public abstract View initView();这个是一个抽象的方法,所对应的public abstract class BasePager 类也必须是抽象的,也就是由abstract关键字。
  5. public Context conetxt;要修改为public,如果自生成时可能是这样的,private final Context conetxt;因为这个conetxt要被其它的类调用。
package com.atguigu.mysql_dome.Base;
import android.content.Context;
import android.view.View;
public abstract class BasePager {
    public Context conetxt;
    public View rootview;
    //本类的构造函数
    public BasePager(Context context){
        this.conetxt=context;
        rootview=initView();
    }
    //初始化视图的抽象类。强制由继承本类的其它类来实现
    public abstract View initView();
    //初始化视图的空方法,执行联网、数据请求、数据显示操作,本空方法不能缺少。
    public void initData(){
    }
}

新建一个子页面

在工程中新建一个包(pager)来存放各子页面,在其包下新建一个EthMainPager的子页面,
代码如下:初步的实现了构造方法,initView()方法和initData方法。
其在程序中显示“初始化了EthMainPager”的文字。

package cn.plczl.mysql.pager;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.widget.TextView;
import cn.plczl.mysql.base.BasePager;
import cn.plczl.mysql.utils.LogUtil;
public class EthMainPager extends BasePager {
    private TextView textView;
    public EthMainPager(Context context) {
        super(context);
    }
    @Override
    public View initView() {
        textView=new TextView(conetxt);
        textView.setGravity(Gravity.CENTER);
        textView.setTextSize(20);
        return textView;
    }
    @Override
    public void initData() {
        super.initData();
        LogUtil.e("初始化了EthMainPager");
        textView.setText("初始化了EthMainPager");
    }
}

将EthMainPager页面放入MainActivity中并显示。

修改MainActivity,为程序扩展,将EthMainPager放入一个basePagers的集合中,自定义了setFragment、getEthMainPager方法,新建了一个ReplacePager类

public class MainActivity extends FragmentActivity {

    private ArrayList<BasePager> basePagers;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        basePagers = new ArrayList<>();
        basePagers.add(new EthMainPager(this));
        setFragment();

    }
    private void setFragment() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
           //这里采用了replace方法,如果只是一个页面,也可以采用add方法。
        fragmentTransaction.replace(R.id.fl_main,new ReplacePager(getEthMainPager()));
        fragmentTransaction.commit();
    }
    private BasePager getEthMainPager() {
        BasePager basePager=basePagers.get(0);
        if(basePager!=null){
            basePager.initData();
        }
        return basePager;
    }
    public static class ReplacePager extends Fragment {

        public BasePager currPager;

        public ReplacePager(BasePager basePager) {

            this.currPager=basePager;

        }

        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }

        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

            return currPager.rootView;
        }
    }

}   

为使程序看起来简捷,将public static class ReplacePager extends Fragment抽取出来形成一个单独的类。
将其抽取至base目录中。文件名为ReplacePager
在MainActivity加入包含import cn.plczl.mysql.base.ReplacePager;

package cn.plczl.mysql.base;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class ReplacePager extends Fragment {
    public BasePager currPager;
    public ReplacePager(BasePager basePager) {
        this.currPager=basePager;
    }
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return currPager.rootView;
    }
}

初步运行界面

以上代码完成初步的界面搭建,初步运行起来的画面如下:

子页面的完善编写

接下来的基本操作都将在EthMainPager子页面中去实现界面,联网和数据请示以及数据显示的操作。

子页面的布局

在EthMainPager的public View initView() 加入一条View view=View.inflate(conetxt, R.layout.ethmainpager,null);,用于加载ethmainpager.xml布局文,不再采用上面程序中的如下代码来初始化子页面了,initView体现了页面的初始化,

textView=new TextView(conetxt);
textView.setGravity(Gravity.CENTER);
textView.setTextSize(20);
return textView;
 @Override
    public View initView() {
        View view=View.inflate(conetxt, R.layout.ethmainpager,null);

        return view;
    }

ethmainpager.xml文件

这里采用简单的一个线性布局,里面有四个TextView,这个程序是Dome性质,只要参调出MySQL数据一张表中库中的两个数据和另一张表中的一个数据,
这里设定能调出数据库最近更新的日期和时间就完成任务。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#23D3FE">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp"
        android:layout_marginRight="20dp"
        android:layout_marginLeft="20dp">
        <TextView
            android:id="@+id/tv_1"
            android:layout_width="wrap_content"
            android:layout_height="25dp"
            android:text="数据更新时间"
            android:padding="2dp"
            android:textSize="16sp"
            android:gravity="left"
            android:layout_marginLeft="5dp"/>
        <TextView
            android:id="@+id/tv_data1"
            android:layout_width="wrap_content"
            android:layout_height="25dp"
            android:text="2020-01-27"
            android:padding="2dp"
            android:textSize="16sp"
            android:gravity="left"
            android:layout_toRightOf="@id/tv_1"
            android:layout_marginLeft="5dp"/>
        <TextView
            android:id="@+id/tv_time"
            android:layout_width="wrap_content"
            android:layout_height="25dp"
            android:text="21:00:00"
            android:padding="2dp"
            android:textSize="16sp"
            android:gravity="left"
            android:layout_toRightOf="@id/tv_data1"
            android:layout_marginRight="20dp"/>
    </RelativeLayout>
<TextView
        android:id="@+id/tv_version"
        android:layout_width="wrap_content"
        android:layout_height="25dp"
        android:text="21:00:00"
        android:padding="2dp"
        android:textSize="16sp"
        android:gravity="left"
        android:layout_marginLeft="22dp"
        android:layout_toRightOf="@id/tv_data"
        android:layout_marginRight="20dp"/>
</LinearLayout>

实例化控件

将用于要显进日期和时间的两个TextView实例化。

    private TextView tv_data;
    private TextView tv_time;
    private TextView tv_version;
    public EthMainPager(Context context) {
        super(context);
    }

    @Override
    public View initView() {
        View view = View.inflate(conetxt, R.layout.ethmainpager,null);
        tv_data = view.findViewById(R.id.tv_data);
        tv_time = view.findViewById(R.id.tv_time);
        tv_version = view.findViewById(R.id.tv_version);
        return view;
    }

导入jar包

采用mysql-connector-java-5.1.30-bin.jar进行MySQL连接,
将下载好的mysql-connector-java-5.1.30-bin.jar复制到工程的libs文件夹下,然点在mysql-connector-java-5.1.30-bin.jar点右键——>点击Add As Library...
完成后build.gradle文件中有这个包的加入。
导入后的build.gradle如下,最后一行代码就是导入了mysql-connector-java-5.1.30-bin.jar

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"
    defaultConfig {
        applicationId "cn.plczl.myapplication"
        minSdkVersion 19
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.13'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation files('libs/mysql-connector-java-5.1.30-bin.jar')
}

数据库连接工具类

编写了连接数据库的工具类DBUtils.jave。用于要对数据操作的调用,代码如下:
其中有三个方法:

  1. getConn用于连接数据,其中String dbName参数为要连接的数据名字,
  2. getEthData方法是获得eth数据库中的apidate表的最新一行的数据方法。
  3. getVersion方法是eth数据库中的version表中的version字段的的最新一行的数据方法。
    调用的方法分别以一个数组和字符串返回。
package cn.plczl.mysql.utils;

import android.util.Log;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;

//连接mySql数据库方法
public class DBUtils {
    private static final String TAG ="Dbutils";
    private static String driver="com.mysql.jdbc.Driver";
//这里是MySQL的用户名
    private static String user="root";
//这里是我的MySQL密码,本文作了隐藏处理,
    private static String password = "xxxxxxx";

    public static Connection getConn(String dbName){
        Connection connection = null;
        try{
            Class.forName(driver);
//数据的IP地址,本文中的地址不是我的真实地址,请换为你的真实IP地址。
            String ip="39.10.011";
            String port="3306";
            String url = "jdbc:mysql://" + ip + ":" + port +"/" + dbName;
            connection= DriverManager.getConnection(url, user, password);
            Log.e("数据库连接", "成功!");
        } catch (Exception e) {
            Log.e("数据库连接", "失败!");
            e.printStackTrace();
        }
        return connection;
    }
    public static String[] getEthData() {
        String rsdata[]=new String[24];
        Connection connection = getConn("eth");
        if (connection!=null){
            String sql="select * from apidate order by id DESC limit 1";
            try{
                java.sql.Statement statement = connection.createStatement();
                ResultSet rSet = statement.executeQuery(sql);
                while (rSet.next()) {
                    rsdata[0]=rSet.getString("date");//日期
                    rsdata[1]=rSet.getString("time");//时间
                    rsdata[2]=rSet.getString("capital").substring(0,4);//人民币总收益
                    rsdata[3]=rSet.getString("worker_length");//矿机总数
                    rsdata[4]=rSet.getString("worker_lenght_online");//在线矿机数
                    rsdata[5]=rSet.getString("worker_length_offline");//离线矿机数
                    rsdata[6]=rSet.getString("dead_sl");//失线矿机数
                    rsdata[7]=rSet.getString("hash_24_hour");//24小时平均算力
                    rsdata[8]=rSet.getString("last_day_value").substring(0,9);//过去24小时收益
                    rsdata[9]=rSet.getString("hash_15");//15分钟平均算力
                    rsdata[10]=rSet.getString("balance").substring(0,9);//当前余额
                    rsdata[11]=rSet.getString("local_hash");//本地算力
                    rsdata[12]=rSet.getString("value").substring(0,9);//总收益
                    rsdata[13]=rSet.getString("quanwangsl")+" TH/s";//全网算力
                    rsdata[14]=rSet.getString("f2poolsl")+" TH/s";//全网算力
                    rsdata[15]=rSet.getString(
                            "meiM_Eth")+" ETH ≈ ¥"
                            +String.valueOf(Double.valueOf(rSet.getString("meiM_Eth"))
                            *Double.valueOf(rSet.getString("meiyuanzhisu"))
                            *Double.valueOf(rSet.getString("usdCnyRate")))
                            .substring(0,5);//每M收益
                    rsdata[16]= String.valueOf(
                            Double.valueOf(rSet.getString("waikuangnandu"))/10000/10000/10000/1000)
                            .substring(0,5)+" P";//当前难度
                    rsdata[17]="#34;+rSet.getString(
                            "price")+" ≈  ¥"
                            +String.valueOf(Double.valueOf(rSet.getString("price"))
                            *Double.valueOf(rSet.getString("usdCnyRate")))
                            .substring(0,4);//币价usdCnyRate
                    rsdata[18]="¥"+rSet.getString("ETC_index");//etc指数
                    rsdata[19]=rSet.getString("ETC_zhangdie");//etc涨跌
                    rsdata[20]=rSet.getString("ETH_zuigaojia");//eth最高价
                    rsdata[21]=rSet.getString("ETH_zuidijia");//eth最低价
                    rsdata[22]=rSet.getString("riseandfall");//ETH涨跌
                    rsdata[23]=rSet.getString("id");//数据库数据条数
                    Log.e(TAG,"数组组装成功");
                }
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        };
        Log.e(TAG,"数组已返回");
        return rsdata;
    }
    public static String getVersion() {
        String version=null;
        Connection connection = getConn("eth");
        if (connection!=null){
            String sql="select version from version";
            try{
                java.sql.Statement statement = connection.createStatement();
                ResultSet rSet = statement.executeQuery(sql);
                while (rSet.next()) {
                    version=rSet.getString("version");//日期
                    Log.e(TAG,"数组组装成功==="+version);
                }
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        };
        Log.e(TAG,"数组已返回==========");
        return version;
    }
}

获得数据更新

在EthMainPagerr的initData()方法中写了两个方法,分别连接两个数据库获得数据并显示。
getData()方法是调用DBUtils数据库连接工具中的getEthData()
getVersion();方法是调用DBUtils数据库连接工具中的getVersion()
采用在子线程中连接数据库和Handler切换到主线程进行数据更新。

 @Override
    public void initData() {
        super.initData();
        LogUtil.e("初始化了EthMainPager");

        getData();
        getVersion();
    }

getData()

简单的new了一个Thread子线程,用于调用 ethdata=DBUtils.getEthData();方法,将数据存入ethdata数组中。通过handler.sendEmptyMessage(0);返回主线程。

     private void getData() {
        new Thread(){
            @Override
            public void run() {
                super.run();
                ethdata=DBUtils.getEthData();
                handler.sendEmptyMessage(0);
            }
        }.start();
    }

handler

在handler中更换tv_data.setText(ethdata[0]);tv_time.setText(ethdata[1]);获得的数据。

 private Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            LogUtil.e(String.valueOf(ethdata.length));
            tv_data.setText(ethdata[0]);
            tv_time.setText(ethdata[1]);
        }
    };

getVersion()

与 getData()类似。
version=DBUtils.getVersion();方法,将数据存入version数组中。通过versionhandle.sendEmptyMessage(1);返回主线程。

    private void getVersion() {
        new Thread(){
            @Override
            public void run() {
                super.run();
                version=DBUtils.getVersion();
                versionhandle.sendEmptyMessage(1);
            }
        }.start();
    }

versionhandle

在versionhandle中更新tv_version.setText(version);获得的数据。

    private Handler versionhandle = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            LogUtil.e(String.valueOf(ethdata.length));
            tv_version.setText(version);
        }
    };

最终运行的结果

软件运行结果

数据库中的数据

相关推荐

linux安装FTP

1、在nkftp目录下安装ftp,进入到nkftp里面[root@localhostbin]#cd/data/nkftp执行安装命令:[root@localhostnkftp]#rpm-i...

LINUX下搭建FTP服务器

FTP服务器介绍FTP是FileTransferProtocol(文件传输协议)的英文简称,而中文简称为“文传协议”。用于Internet上的控制文件的双向传输。同时,它也是一个应用程序(App...

Linux下如何进行FTP设置

目录:一、Redhat/CentOS安装vsftp软件二、Ubuntu/Debian安装vsftp软件一、Redhat/CentOS安装vsftp软件1.更新yum源yumupdate-y2.安...

推荐使用集串口 SSH远程登录和FTP传输三合一工具MobaXterm

来源:百问网作者:韦东山本文字数:1216,阅读时长:4分钟在以前的资料里,串口和SSH远程登使用SecureCRT,window与ubuntu数据传输使用filezilla,窗口切换来切换去,麻烦也...

如何搭建FTP服务器(Linux系统)

上次说了Windows操作系统下搭建的FTP服务器,那有朋友问我,说买的XX轻量应用服务器都是属于Linux的操作系统,我该如何为搭建FTP服务器呢?...

Linux 命令 ncftp(文件传输)——想玩转linux就请一直看下去

我是IT悟道,点击右上方“关注”,每天分享IT、科技、数码方面的干货。Linuxncftp命令...

如何用 ftp 实现一键上传

简介ftp是Internet标准文件传输协议的用户界面,它允许用户与远程网络站点之间传输文件...

Linux安装ftp

1安装vsftpd组件安装完后,有/etc/vsftpd/vsftpd.conf文件,是vsftp的配置文件。[root@bogon~]#yum-yinstallvsftpd2添加一个...

一天一点点:linux - ftp命令

linuxftp命令设置文件系统相关功能。FTP是ARPANet的标准文件传输协议,该网络就是现今Internet的前身。语法ftp[-dignv][主机名称或IP地址]参数:-d详细显示指令执...

Centos 7 搭建FTP

目录安装软件以及启动服务添加防火墙规则关闭selinuxftp配置常用常用参数详解特殊参数配置文件没有的参数也可以添加到配置中1.安装软件以及启动服务yuminstall-yvsftpdsys...

【Linux】Linux中ftp命令,没有你想的那么简单

本文介绍了Linux中FTP命令的基本用法,包括连接与登录远程服务器,以及解析了FTP协议中五个最常用的操作命令的使用和解析过程。同时,提供了一个包含常用FTP操作命令的表格,供读者参考。通过熟练掌握...

linux 命令行操作ftp

以下是linuxftp命令参数的详解。FTP>!从ftp子系统退出到外壳?FTP>?显示ftp命令说明??和help相同?格式:?[command]说明:[com...

多学习才能多赚钱之:linux如何使用ftp

linux如何使用ftp步骤1:建立FTP连接想要连接FTP服务器,在命令上中先输入ftp然后空格跟上FTP服务器的域名'domain.com'或者IP地址例如:ftpdom...

linux常用网络操作方法:ftp命令使用方法

常用网络操作方法Linux提供了一组强有力的网络命令来为用户服务,这些工具能够帮助用户登录到远程计算机上、传输文件和执行远程命令等。本节介绍下列几个常用的有关网络操作的命令:ftp传输文件tel...

Linux 5.15有望合并Memory Folios方案 内核构建速度可提升7%

甲骨文公司的长期内核开发人员MatthewWilcox已经研究了“内存对开区”概念相当长的一段时间,这可以改善Linux的内存管理,使其具有更大的效率。例如,使用内存对开的基准测试表明,内核的构建速...

取消回复欢迎 发表评论: