java http測試工具類

第一種:

<dependency>
    <groupId>commons-httpclient</groupId>
    <artifactId>commons-httpclient</artifactId>
    <version>3.1</version>
</dependency>

注:類中 StaticProps.DEFAULT_CHART 請自行替換爲 utf-8  ;LogUtil.printInfo 方法自行替換爲自己的日誌打印方法

第二種方法也是自行替換


import com.alibaba.fastjson.JSON;
import com.demo.property.StaticProps;
import com.demo.util.log.LogUtil;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.params.DefaultHttpParams;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.lang.StringUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * HttpClient工具類,版本:3.1
 *
 * @author qiaojun
 */
public class HttpClient3Util
{
    private static String VERSION = "(3.1)";

    private static HttpClient CLIENT;

    private static class SingletonHolder
    {
        private static HttpClient3Util INSTANCE = new HttpClient3Util();
    }

    public static HttpClient3Util getInstance()
    {
        return SingletonHolder.INSTANCE;
    }

    private HttpClient3Util()
    {
        MultiThreadedHttpConnectionManager manager = new MultiThreadedHttpConnectionManager();
        manager.getParams().setConnectionTimeout(10000);
        manager.getParams().setSoTimeout(10000);
        manager.getParams().setDefaultMaxConnectionsPerHost(20);
        manager.getParams().setMaxTotalConnections(200);
        // manager.closeIdleConnections(60L);
        // manager.deleteClosedConnections();

        // HttpClientParams params = new HttpClientParams();
        // params.setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(0, false)); // 重試機制
        HttpClientParams params = new HttpClientParams(DefaultHttpParams.getDefaultParams()); // 使用默認配置
        CLIENT = new HttpClient(params, manager);
    }

    public String doGet(String url, Map<String, Object> params)
    {
        return httpGet(url, params, StaticProps.DEFAULT_CHART);
    }

    public String doPostMap(String url, Map<String, Object> params)
    {
        return httpPost(url, params, StaticProps.DEFAULT_CHART);
    }

    public String doPostJson(String url, Object obj)
    {
        return httpPost(url, null != obj ? JSON.toJSONString(obj) : null, StaticProps.DEFAULT_CHART, 0);
    }

    public String doPostStr(String url, String jsonStr)
    {
        return httpPost(url, jsonStr, StaticProps.DEFAULT_CHART, 0);
    }

    public String httpGet(String url, Map<String, Object> params, String chartSet)
    {
        GetMethod method = initGetMethod(url);
        if (null != params && !params.isEmpty())
        {
            LogUtil.printInfo("HTTP(GET) P_MAP {} : {}", VERSION, params.toString());
            method.setParams(assembleMethodParams(params));
        }
        return sendGet(method, chartSet);
    }

    public String httpPost(String url, Map<String, Object> params, String chartSet)
    {
        PostMethod method = initPostMethod(url);
        if (null != params && !params.isEmpty())
        {
            LogUtil.printInfo("HTTP(POST) P_MAP {} : {}", VERSION, params.toString());
            method.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, chartSet);
            method.setRequestBody(assembleNvParams(params));
        }
        return sendPost(method, chartSet);
    }

    public String httpPost(String url, String str, String chartSet, int contentType)
    {
        PostMethod method = initPostMethod(url);
        if (StringUtil.isNotNullEmpty(str))
        {
            LogUtil.printInfo( "HTTP(POST) P_STR {} : {}", VERSION, str);
            RequestEntity se = null;
            try
            {
                se = new StringRequestEntity(str, 0 == contentType ? "application/json" : "application/x-www-form-urlencoded", chartSet);
            }
            catch (UnsupportedEncodingException e)
            {
                LogUtil.printError(e.getMessage(), e);
            }
            method.setRequestEntity(se);
        }
        return sendPost(method, chartSet);
    }

    private String sendGet(GetMethod method, String chartSet)
    {
        String result = null;
        try
        {
            int statusCode = CLIENT.executeMethod(method);
            if(HttpStatus.SC_OK == statusCode)
            {
                result = new String(method.getResponseBodyAsString().getBytes(), chartSet);
            }
            else
            {
                LogUtil.printWarn("HTTP(GET) CODE {} : {}", VERSION, statusCode);
            }
        }
        catch (IOException e)
        {
            LogUtil.printError(e.getMessage(), e);
        }
        finally
        {
            method.releaseConnection();
        }
        LogUtil.printInfo("HTTP(GET) RESP {} : {}", VERSION, result);
        return result;
    }

    private String sendPost(PostMethod method, String chartSet)
    {
        String result = null;
        try
        {
            // LogUtil.printInfo("HTTP(POST) CONTENT: " + Arrays.toString(method.getParameters()));
            int statusCode = CLIENT.executeMethod(method);
            if (HttpStatus.SC_OK == statusCode)
            {
                if (null != method.getResponseBodyAsStream())
                {
                    // result = new String(method.getResponseBodyAsString().getBytes(), chartSet);
                    InputStreamReader isReader = new InputStreamReader(method.getResponseBodyAsStream());
                    BufferedReader bufferReader = new BufferedReader(isReader);
                    result = bufferReader.lines().collect(Collectors.joining());
                    bufferReader.close();
                    isReader.close();
                }
            }
            else
            {
                LogUtil.printWarn("HTTP(POST) CODE {} : {}", VERSION, statusCode);
            }
        }
        catch (Exception e)
        {
            LogUtil.printError(e.getMessage(), e);
        }
        finally
        {
            method.releaseConnection();
        }
        LogUtil.printInfo("HTTP(POST) RESP {} : {}", VERSION, result);
        return result;
    }

    private GetMethod initGetMethod(String url)
    {
        LogUtil.printDebug( "HTTP(GET) URL {} : {}", VERSION, url);
        return new GetMethod(StringUtils.deleteWhitespace(url));
    }

    private PostMethod initPostMethod(String url)
    {
        LogUtil.printDebug( "HTTP(POST) URL {} : {}", VERSION, url);
        return new PostMethod(StringUtils.deleteWhitespace(url));
    }

    /**
     * 組裝httpPost請求參數
     * @return NameValuePair[]
     */
    private NameValuePair[] assembleNvParams(Map<String, Object> data)
    {
        List<NameValuePair> nameValueList = new ArrayList<>();
        data.forEach((key, value) -> nameValueList.add(new NameValuePair(key, value.toString())));
        return nameValueList.toArray(new NameValuePair[nameValueList.size()]);
    }

    /**
     * 組裝Map請求參數
     * @return HttpMethodParams
     */
    private HttpMethodParams assembleMethodParams(Map<String, Object> data)
    {
        HttpMethodParams params = new HttpMethodParams();
        data.forEach(params::setParameter);
        return params;
    }
}

使用示例:

//post  key=value 格式
public static void test(){
       
        Map<String, Object> map = new HashMap<>();
        map.put("key","value");
        map.put("key1","value1");
     
        String result = HttpClient3Util.getInstance().httpPost("url", map,"UTF-8");
        System.out.println(result);
    }
//post  json字符串 格式
 private static void getTimeConsumingDate(){
        Map<String, Object> map = new HashMap<>();
        map.put("key","value");
        map.put("key1","value1");

        String result = HttpClient3Util.getInstance().doPostStr("url", JSONObject.toJSONString(map));
        System.out.println(result);
    }

//get方法類似,自己實際用過就知道

第二種:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.5</version>
</dependency>

import com.alibaba.fastjson.JSON;
import com.demo.property.StaticProps;
import com.demo.util.log.LogUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.pool.PoolStats;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * HttpClient工具類,版本:4.5
 *
 * @author qiaojun
 */
public class HttpClient4Util
{
    private static CloseableHttpClient CLIENT;

    private static CloseableHttpClient CLIENT_RETRY;

    private static Boolean RETRY;

    private static class SingletonHolder
    {
        private static HttpClient4Util INSTANCE = new HttpClient4Util();
    }

    public static HttpClient4Util getInstance()
    {
        return SingletonHolder.INSTANCE;
    }

    public static HttpClient4Util getInstance(Boolean retry)
    {
        RETRY = null == retry ? false : retry;
        return SingletonHolder.INSTANCE;
    }

    private HttpClient4Util()
    {
        ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslsf  = SSLConnectionSocketFactory.getSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create()
                .register("http", plainsf)
                .register("https", sslsf)
                .build();

        PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(registry);
        manager.setMaxTotal(200);
        manager.setDefaultMaxPerRoute(20);
        // manager.closeExpiredConnections();
        // manager.closeIdleConnections(30L, TimeUnit.SECONDS);
        // manager.setValidateAfterInactivity(30000);

        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(5000)    //客戶端和服務器建立連接的timeout
                .setConnectionRequestTimeout(3000)  //從連接池獲取連接的timeout
                .setSocketTimeout(8000) //客戶端從服務器讀取數據的timeout
                .build();

        CLIENT = HttpClients.custom()
                .setConnectionManager(manager)
                .setDefaultRequestConfig(config)
                .setRetryHandler(new DefaultHttpRequestRetryHandler(0, false))
                .build();

        CLIENT_RETRY = HttpClients.custom()
                .setConnectionManager(manager)
                .setDefaultRequestConfig(config)
                .setRetryHandler(getRetryHandler())
                .setKeepAliveStrategy(getKeepAliveStrategy())
                // .evictIdleConnections(30L, TimeUnit.SECONDS)
                // .evictExpiredConnections()
                .build();

        RETRY = false;

        // 啓動連接回收策略線程
        new HttpClient4Monitor(manager).start();
    }

    private ConnectionKeepAliveStrategy getKeepAliveStrategy()
    {
        return (response, context) ->
        {
            // Honor 'keep-alive' header
            HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (it.hasNext())
            {
                HeaderElement he = it.nextElement();
                String param = he.getName();
                String value = he.getValue();
                if (value != null && param.equalsIgnoreCase("timeout"))
                {
                    try
                    {
                        return Long.parseLong(value) * 1000;
                    }
                    catch(NumberFormatException e)
                    {
                        LogUtil.printWarn(e.getMessage(), e);
                    }
                }
            }
            HttpHost target = (HttpHost) context.getAttribute(HttpClientContext.HTTP_TARGET_HOST);
            if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName()))
            {
                // Keep alive for 5 seconds only
                return 5 * 1000;
            }
            else
            {
                // otherwise keep alive for 30 seconds
                return 30 * 1000;
            }
        };
    }

    private HttpRequestRetryHandler getRetryHandler()
    {
        return (e, execCount, httpContext) ->
        {
            // 重試次數
            if (execCount >= 3) {
                return false;
            }
            // 未知服務器
            if (e instanceof UnknownHostException) {
                return false;
            }
            // 服務器丟掉連接
            if (e instanceof NoHttpResponseException) {
                return true;
            }
            // SSL異常(包含握手異常)
            if (e instanceof SSLException) {
                return false;
            }
            // IO傳輸中斷(包含connect+socket超時)
            if (e instanceof InterruptedIOException) {
                return true;
            }
            // 判斷請求是冪等 再次嘗試
            HttpClientContext clientContext = HttpClientContext.adapt(httpContext);
            return !(clientContext.getRequest() instanceof HttpEntityEnclosingRequest);
        };
    }

    private void setHeader(HttpRequestBase http)
    {
        http.setHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) " +
                "Gecko/2008092417 Firefox/3.0.3");
        http.setHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
        http.setHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"); // "en-US,en;q=0.5";
        http.setHeader("Accept-Charset","ISO-8859-1,utf-8,gbk,gb2312;q=0.7,*;q=0.7");
        http.setHeader("Keep-Alive", "300");
        http.setHeader("Connection", "Keep-Alive");
        http.setHeader("Cache-Control", "no-cache");
    }

    public String doGet(String url, Map<String, Object> params)
    {
        return httpGet(url, params, StaticProps.DEFAULT_CHART);
    }

    public String doPostMap(String url, Map<String, Object> params)
    {
        return httpPost(url, params, StaticProps.DEFAULT_CHART);
    }

    public String doPostJson(String url, Object obj)
    {
        return doPostStr(url, null != obj ? JSON.toJSONString(obj) : null, 0);
    }

    public String doPostStr(String url, String jsonStr, int contentType)
    {
        return httpPost(url, jsonStr, StaticProps.DEFAULT_CHART, contentType);
    }

    public String httpGet(String url, Map<String, Object> params, String chartSet)
    {
        HttpGet method = initGetMethod(url);
        if (null != params && !params.isEmpty())
        {
            LogUtil.printInfo("HTTP(GET) P_MAP : {}", params.toString());
            try
            {
                String uriStr = EntityUtils.toString(new UrlEncodedFormEntity(assembleNvParams(params), chartSet));
                method.setURI(new URI(method.getURI().toString() + "?" + uriStr));
            }
            catch (IOException | URISyntaxException e)
            {
                LogUtil.printError(e.getMessage(), e);
            }
        }
        return sendGet(method, chartSet);
    }

    public String httpPost(String url, Map<String, Object> params, String chartSet)
    {
        HttpPost method = initPostMethod(url);
        if (null != params && !params.isEmpty())
        {
            LogUtil.printInfo("HTTP(POST) P_MAP : {}", params.toString());
            try
            {
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(assembleNvParams(params), chartSet);
                method.setEntity(entity);
            }
            catch (IOException e)
            {
                LogUtil.printError(e.getMessage(), e);
            }
        }
        return sendPost(method, chartSet);
    }

    public String httpPost(String url, String str, String chartSet, int contentType)
    {
        HttpPost method = initPostMethod(url);
        if (StringUtil.isNotNullEmpty(str))
        {
            LogUtil.printInfo("HTTP(POST) P_STR : {}", str);
            StringEntity entity = new StringEntity(str, chartSet);
            entity.setContentType(0 == contentType ? "application/json" : "application/x-www-form-urlencoded");
            entity.setContentEncoding(chartSet);
            method.setEntity(entity);
        }
        return sendPost(method, chartSet);
    }

    public String httpPostXmlStr(String url, String xmlStr, String chartSet){
        if(StringUtils.isEmpty(chartSet)){
            chartSet="utf-8";
        }
        HttpPost method = new HttpPost(url);
        StringEntity entityParams = new StringEntity(xmlStr,"utf-8");
        entityParams.setContentType("text/xml");
        method.setEntity(entityParams);
        return sendPost(method, chartSet);
    }

    public String httpPostXmlStr(String url, String xmlStr, String chartSet,String token){
        if(StringUtils.isEmpty(chartSet)){
            chartSet="utf-8";
        }
        LogUtil.printInfo("HTTP(POST) REQ : {}", xmlStr);
        HttpPost method = new HttpPost(url);
        method.setHeader("token", token);
        StringEntity entityParams = new StringEntity(xmlStr,"utf-8");
        entityParams.setContentType("text/xml");
        method.setEntity(entityParams);
        return sendPost(method, chartSet);
    }

//    /**
//     * 發送xml請求到server端
//     * @param url xml請求數據地址
//     * @param xmlString 發送的xml數據流
//     * @return null發送失敗,否則返回響應內容
//     */
//    public  String sendPostxml(String url,String xmlString){
//        //創建httpclient工具對象
//        //創建post請求方法
//        HttpPost method = initPostMethod(url);
//        //設置請求超時時間
//        String responseString = null;
//        try{
//            //設置請求頭部類型
//            method.setRequestHeader("Content-Type","text/xml");
//            myPost.setRequestHeader("charset","utf-8");
//            //設置請求體,即xml文本內容,一種是直接獲取xml內容字符串,一種是讀取xml文件以流的形式
//            method.set(xmlString);
//            int statusCode = client.executeMethod(myPost);
//            //只有請求成功200了,才做處理
//            if(statusCode == HttpStatus.SC_OK){
//                InputStream inputStream = myPost.getResponseBodyAsStream();
//                BufferedReader br = new BufferedReader(new InputStreamReader(inputStream,"utf-8"));
//                StringBuffer stringBuffer = new StringBuffer();
//                String str = "";
//                while ((str = br.readLine()) != null) {
//                    stringBuffer.append(str);
//                }
//                responseString = stringBuffer.toString();
//            }
//        }catch (Exception e) {
//            e.printStackTrace();
//        }finally{
//            myPost.releaseConnection();
//        }
//        return responseString;
//    }

    private String sendGet(HttpGet method, String chartSet)
    {
        String result = null;
        CloseableHttpResponse resp = null;
        try
        {
            resp = RETRY ? CLIENT_RETRY.execute(method) : CLIENT.execute(method);
            result = EntityUtils.toString(resp.getEntity(), chartSet);
            EntityUtils.consume(resp.getEntity());
        }
        catch (IOException e)
        {
            LogUtil.printError(e.getMessage(), e);
        }
        finally
        {
            method.releaseConnection();
            if (null != resp)
            {
                try
                {
                    resp.close();
                }
                catch (IOException e)
                {
                    LogUtil.printError(e.getMessage(), e);
                }
            }
        }
        LogUtil.printInfo("HTTP(GET) RESP : {}", result);
        return result;
    }

    private String sendPost(HttpPost method, String chartSet)
    {
        String result = null;
        CloseableHttpResponse resp = null;
        try
        {
            resp = RETRY ? CLIENT_RETRY.execute(method) : CLIENT.execute(method);
            StatusLine statusLine = resp.getStatusLine();
            if(HttpStatus.SC_OK == statusLine.getStatusCode())
            {
                result = EntityUtils.toString(resp.getEntity(), chartSet);
                EntityUtils.consume(resp.getEntity());
            }
            else
            {
                LogUtil.printWarn("HTTP(POST) CODE : {}", statusLine.getStatusCode());
            }
        }
        catch (IOException e)
        {
            LogUtil.printError(e.getMessage(), e);
        }
        finally
        {
            method.releaseConnection();
            if(null != resp)
            {
                try
                {
                    resp.close();
                }
                catch (IOException e)
                {
                    LogUtil.printError(e.getMessage(), e);
                }
            }
        }
        LogUtil.printInfo("HTTP(POST) RESP : {}", result);
        return result;
    }

    private HttpGet initGetMethod(String url)
    {
        LogUtil.printDebug( "HTTP(GET) URL : {}", url);
        return new HttpGet(StringUtils.deleteWhitespace(url));
    }

    private HttpPost initPostMethod(String url)
    {
        LogUtil.printDebug( "HTTP(POST) URL : {}", url);
        return new HttpPost(StringUtils.deleteWhitespace(url));
    }

    /**
     * 組裝httpPost請求參數
     * @return List<NameValuePair>
     */
    private List<NameValuePair> assembleNvParams(Map<String, Object> data)
    {
        List<NameValuePair> list = new ArrayList<>();
        data.forEach((key, value) ->
        {
            BasicNameValuePair basicNameValuePair = new BasicNameValuePair(key, value.toString());
            list.add(basicNameValuePair);
        });
        return list;
    }

    /**
     * Http線程池監聽器
     *
     */
    class HttpClient4Monitor extends Thread
    {
        private final PoolingHttpClientConnectionManager connMgr;

        private volatile boolean shutdown;

        HttpClient4Monitor(PoolingHttpClientConnectionManager connMgr)
        {
            super();
            this.setName("Http Connection Monitor");
            this.setDaemon(true);
            this.connMgr = connMgr;
        }

        @Override
        public void run()
        {
            while (!shutdown)
            {
                synchronized (this)
                {
                    try
                    {
                        wait(30000L);
                    }
                    catch (InterruptedException e)
                    {
                        LogUtil.printWarn(e.getMessage());
                    }
                    PoolStats stats = connMgr.getTotalStats();
                    if (stats.getAvailable() > 0)
                    {
                        // 關閉60秒內不活動的連接 可選
                        connMgr.closeIdleConnections(60L, TimeUnit.SECONDS);
                        LogUtil.printDebug("HTTP CONNECTION POOL STATS : {}\n", stats.toString());
                    }
                    // 關閉失效的連接
                    connMgr.closeExpiredConnections();
                }
            }
        }

        public void shutdown()
        {
            shutdown = true;
            synchronized (this)
            {
                notifyAll();
            }
        }
    }

}

使用示例:

//post key value
public static void test(){
        Map<String, Object> map = new HashMap<>();
        
        map.put("userId", "sfdsfd");
        String result = HttpClient4Util.getInstance().httpPost("url", map,"UTF-8");
        System.out.println(result);
    }
//post json字符串
    public static void test(){
    
        Map<String, String> map = new HashMap<>();
        map.put("key", "va");
        map.put("key1", "va");
       
     

        String result = HttpClient4Util.getInstance().httpPostXmlStr
                ("/url",  JSONObject.toJSONString(map), "UTF-8");
    }
//get方法類似

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章