【SSH網上商城項目實戰21】從Demo中看易寶支付的流程

  這一節我們先寫一個簡單點的Demo來測試易寶支付的流程,熟悉這個流程後,再做實際的開發,因爲是一個Demo,所以我沒有考慮一些設計模式的東西,就是直接實現支付功能。實現支付功能需要易寶給我們提供的API。那麼問題來了,使用第三方支付平臺最主要的一件事就是獲取該平臺的API,我們首先得獲取他們的API以及開發文檔,然後纔可以做進一步的開發。

1. 獲取易寶的API

  獲取API的第一步,要在易寶上註冊一個賬號,這個賬號是商家的賬號,後面買家付款後,會將錢款存入該賬號中,然後商家自己提取到銀行卡,易寶在提取過程中收取一定的手續費。這就是易寶的盈利模式。但是註冊成功需要前提,那就是自己得有一個網站,或者是一個公司,吧啦吧啦等東西,反正就是你得有資格申請,這點易寶會審覈的,滿足了纔會允許你註冊,纔會給你提供他們的接口,不是所有人都可以註冊的。我用的也是別人註冊好的,我自己啥也沒有……也沒法註冊……屌絲一個,大家懂的~但是一般在公司裏開發的話,就不會存在這個問題,賬號肯定都是有的,最重要的是要掌握開發流程和相關技術~

2. 測試支付流程

  有了官方提供的API和技術文檔後,就可以着手開發了,在這裏主要寫一個簡單的demo來測試一下易寶支付的流程,demo的結構很簡單,一個servlet,一個filter,兩個jsp頁面和一個加密的工具類。servlet與易寶服務器端打交道,我們做一些跟易寶接口相關的處理,filter是用來處理可能出現的中文亂碼問題,兩個jsp中一個是前臺頁面。
  我們先來分析一下支付請求的過程,如下所示:

Created with Raphaël 2.1.0index.jspindex.jspservletservletreqpay.jspreqpay.jsp易寶易寶傳遞部分明文將易寶要求的所有明文整合到StringBuffer中並生成字符串對生成的字符串用md5算法進行加密,形成密文將明文和密文寫到reqpay.jsp中將明文和密文傳給易寶易寶利用傳過來的明文,使用相同的md5加密算法生成密文判斷該生成的密文和前面穿件來的密文是否相同,如果相同則說明有效易寶生成新的明文(包括訂單的信息,流水號,是否處理成功等)利用新的明文生成相應的密文將新的明文和密文傳到servlet獲取新傳來的明文並整合到StringBuffer中並生成字符串對生成的字符串用md5加密算法進行加密,形成密文判斷與易寶傳來的密文是否相等如果相等,並且傳回來的新的明文中的r1_Code字段是1則表示支付成功

  好了,下面我們具體分析一下demo中的相關代碼:

2.1 前臺測試頁面

  首先看一下前臺頁面index.jsp的具體代碼

<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>

    <title>前臺首頁</title>
  </head>

  <body>
    <h1>在線支付演示</h1>
    <form action="${pageContext.request.contextPath }/servlet/PayServlet" method="post">
         此次購物訂單編號<input type="text" name="p2_Order" /><br>
         money<input type="text" name="p3_Amt" value="0.01"/><br>
         工商銀行<input type="radio" value="ICBC-NET" name="pd_FrpId">
         建設銀行<input type="radio" value="CCB-NET" name="pd_FrpId"><br>
         <input type="submit" value="submit" />
         <input type="hidden" value="pay" name="status"/>
    </form>
  </body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

  從上面的jsp頁面中可以看出,這些input標籤中的name屬性值都很奇怪,pi_功能(i=0,1,2,…,9),當然i還有其他的值,這得參照易寶的官方文檔,這些name表示相對應的屬性,到時候會傳到sevlet處理,關於這些屬性值,我截了個圖,如下:
相關屬性
  這些參數名有些在實際項目中是前臺傳進來的,比如上面寫的訂單號,要付多少錢,這些在訂單確認的時候都會帶過去,那麼其他參數,必填的話,需要在servlet裏指定好,非必填字段的話,就可以爲空,這裏的空不是null,而是”“,後面servlet中會提到。
  再看看兩個銀行中對應的value值也是固定的,易寶會提供它所支持的所有銀行的value值,這些都是固定的,不能修改的。這裏就寫兩個銀行測試一下效果。
  最後那個隱藏字段是用來在servlet中做判斷的,是支付還是支付成功後的返回,下面在sevlet中會說明。

2.2 Servlet處理請求

  servlet主要處理與易寶的相關請求,裏面有兩個部分的內容,一部分是向易寶發送明文和密文,另一部分是判斷易寶發過來的明文和密文,我們看看demo中具體的實現代碼:

public class PayServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String status = request.getParameter("status");
        if (status.equals("pay")) { //index.jsp中隱藏字段傳來的是pay,所以處理支付這部分
            // 加密的密鑰,用在加密算法中,由支付中介提供,每個商家獨一無二的
            String keyValue = "w0P75wMZ203fr46r5i70V556WHFa94j14yW5J6vuh4yo3nRl5jsqF3c41677";
            // 1: 給參數賦值,這些參數(即明文)都是易寶官方提供的文檔中所定義的,名字我們不能改
            String p0_Cmd = formatString("Buy");
            String p1_MerId = formatString("10000940764");
            String p2_Order = formatString(request.getParameter("p2_Order"));
            String p3_Amt = formatString(request.getParameter("p3_Amt"));
            String p4_Cur = formatString("CNY");
            String p5_Pid = "";
            String p6_Pcat = "";
            String p7_Pdesc = "";
            String p8_Url = "http://www.tongji.edu.cn";//這是支付成功後跳轉到的頁面,可以設爲商城首頁,這個demo就用同濟大學主頁好了……
            String p9_SAF = "0";
            String pa_MP = "";
            String pd_FrpId = formatString(request.getParameter("pd_FrpId"));
            pd_FrpId = pd_FrpId.toUpperCase();
            String pr_NeedResponse = "0";

            String hmac = formatString("");//hmac是用來存儲密文的
            /*上面所有的明文都用都用formatString方法包裝了一下,該方法在下面,主要是將null轉換成""
             *因爲null是無法轉換成密文的*/

            // 解決數據安全性問題:  把明文加密--->密文    然後把明文和密文都交給易寶 
            // 易寶拿到數據後,把傳過來的明文加密, 和傳過來密文比較,
            // 如果相等數據沒有被篡改 (商家與易寶加密時都用的是相同key)

            // 把明文數據追加到StringBuffer,注意追加順序不能改,否則生成的密文會不同的,
            // 要嚴格按照易寶的官方文檔說名來寫才行,因爲易寶那邊就是根據文檔中的順序追加的
            StringBuffer infoBuffer = new StringBuffer();
            infoBuffer.append(p0_Cmd);
            infoBuffer.append(p1_MerId);
            infoBuffer.append(p2_Order);
            infoBuffer.append(p3_Amt);
            infoBuffer.append(p4_Cur);
            infoBuffer.append(p5_Pid);
            infoBuffer.append(p6_Pcat);
            infoBuffer.append(p7_Pdesc);
            infoBuffer.append(p8_Url);
            infoBuffer.append(p9_SAF);
            infoBuffer.append(pa_MP);
            infoBuffer.append(pd_FrpId);
            infoBuffer.append(pr_NeedResponse);
            // 加密後的密文存儲到了hmac中,加密算法易寶會提供的,因爲他那邊也得用相同的算法
            hmac = DigestUtil.hmacSign(infoBuffer.toString(), keyValue);
            // 把明文和密文都存儲到request.setAttribute中
            request.setAttribute("p0_Cmd", p0_Cmd);
            request.setAttribute("p1_MerId", p1_MerId);
            request.setAttribute("p2_Order", p2_Order);
            request.setAttribute("p3_Amt", p3_Amt);
            request.setAttribute("p4_Cur", p4_Cur);
            request.setAttribute("p5_Pid", p5_Pid);
            request.setAttribute("p6_Pcat", p6_Pcat);
            request.setAttribute("p7_Pdesc", p7_Pdesc);
            request.setAttribute("p8_Url", p8_Url);
            request.setAttribute("p9_SAF", p9_SAF);
            request.setAttribute("pa_MP", pa_MP);
            request.setAttribute("pd_FrpId", pd_FrpId);
            request.setAttribute("pr_NeedResponse", pr_NeedResponse);
            request.setAttribute("hmac", hmac);
            System.out.println("hmac-->" + hmac);
            //跳轉到reqpay.jsp中,將這些信息提交到易寶
            request.getRequestDispatcher("/reqpay.jsp").forward(request,
                    response);
        } else if (status.equals("success")) {//易寶那邊傳來的是success,處理返回驗證部分
            PrintWriter out = response.getWriter();
            String keyValue = "w0P75wMZ203fr46r5i70V556WHFa94j14yW5J6vuh4yo3nRl5jsqF3c41677";
            // 獲取所有的明文
            String r0_Cmd = formatString(request.getParameter("r0_Cmd")); 
            String p1_MerId = request.getParameter("p1_MerId");
            String r1_Code = formatString(request.getParameter("r1_Code"));
            String r2_TrxId = formatString(request.getParameter("r2_TrxId"));
            String r3_Amt = formatString(request.getParameter("r3_Amt"));
            String r4_Cur = formatString(request.getParameter("r4_Cur"));
            String r5_Pid = new String(formatString(
                    request.getParameter("r5_Pid")).getBytes("iso-8859-1"),
                    "UTF-8");
            String r6_Order = formatString(request.getParameter("r6_Order"));
            String r7_Uid = formatString(request.getParameter("r7_Uid"));
            String r8_MP = new String(formatString(
                    request.getParameter("r8_MP")).getBytes("iso-8859-1"),
                    "UTF-8");
            String r9_BType = formatString(request.getParameter("r9_BType"));
            // 對明文進行數據追加
            String hmac = formatString(request.getParameter("hmac"));
            StringBuffer infoBuffer = new StringBuffer();
            infoBuffer.append(p1_MerId);
            infoBuffer.append(r0_Cmd);
            infoBuffer.append(r1_Code);
            infoBuffer.append(r2_TrxId);
            infoBuffer.append(r3_Amt);
            infoBuffer.append(r4_Cur);
            infoBuffer.append(r5_Pid);
            infoBuffer.append(r6_Order);
            infoBuffer.append(r7_Uid);
            infoBuffer.append(r8_MP);
            infoBuffer.append(r9_BType);
            // 對返回的明文進行加密
            String md5 = DigestUtil.hmacSign(infoBuffer.toString(), keyValue);
            // 判斷加密的密文與傳過來的數據簽名是否相等
            boolean isOK = md5.equals(hmac);
            if (isOK && r1_Code.equals("1")) {//r1_Code爲1表示成功
                //把支付成功的訂單狀態改成已支付,並個給用戶顯示支付成功信息
                //調用郵件服務接口,短信發送服務等
                //這裏就打印一句話唄~
                out.println("訂單編號爲:" + r6_Order + "支付金額爲:" + r3_Amt);

            } else {
                out.println("fail !!!!");
            }
        }
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        doGet(request, response);
    }

    String formatString(String text) {
        if (text == null) {
            return "";
        }
        return text;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130

2.3 加密算法

  明文轉密文所用到的加密算法由易寶提供,我們只需要用它將明文轉爲密文即可,算法如下:

public class DigestUtil {

    private static String encodingCharset = "UTF-8";

    public static String hmacSign(String aValue, String aKey) {
        byte k_ipad[] = new byte[64];
        byte k_opad[] = new byte[64];
        byte keyb[];
        byte value[];
        try {
            keyb = aKey.getBytes(encodingCharset);
            value = aValue.getBytes(encodingCharset);
        } catch (UnsupportedEncodingException e) {
            keyb = aKey.getBytes();
            value = aValue.getBytes();
        }

        Arrays.fill(k_ipad, keyb.length, 64, (byte) 54);
        Arrays.fill(k_opad, keyb.length, 64, (byte) 92);
        for (int i = 0; i < keyb.length; i++) {
            k_ipad[i] = (byte) (keyb[i] ^ 0x36);
            k_opad[i] = (byte) (keyb[i] ^ 0x5c);
        }

        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {

            return null;
        }
        md.update(k_ipad);
        md.update(value);
        byte dg[] = md.digest();
        md.reset();
        md.update(k_opad);
        md.update(dg, 0, 16);
        dg = md.digest();
        return toHex(dg);
    }

    public static String toHex(byte input[]) {
        if (input == null)
            return null;
        StringBuffer output = new StringBuffer(input.length * 2);
        for (int i = 0; i < input.length; i++) {
            int current = input[i] & 0xff;
            if (current < 16)
                output.append("0");
            output.append(Integer.toString(current, 16));
        }

        return output.toString();
    }

    public static String getHmac(String[] args, String key) {
        if (args == null || args.length == 0) {
            return (null);
        }
        StringBuffer str = new StringBuffer();
        for (int i = 0; i < args.length; i++) {
            str.append(args[i]);
        }
        return (hmacSign(str.toString(), key));
    }

    /**
     * @param aValue
     * @return
     */
    public static String digest(String aValue) {
        aValue = aValue.trim();
        byte value[];
        try {
            value = aValue.getBytes(encodingCharset);
        } catch (UnsupportedEncodingException e) {
            value = aValue.getBytes();
        }
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("SHA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
        return toHex(md.digest(value));

    }

    //我自己用來測試的
    public static void main(String[] args) {
        // 參數1: 明文(要加密的數據)  參數2: 密鑰
        System.out.println(DigestUtil.hmacSign("11111", "abc"));
        System.out.println(DigestUtil.hmacSign("11111", "abd"));
    //  解決數據安全性問題:  把明文加密--->密文    然後把明文和密文都交給易寶 
    //  易寶拿到數據後,把傳過來的明文加密, 和傳過來密文比較,如果相等數據沒有被篡改 (商家與易寶加密時都用的是相同key)     
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98

  加密算法也不去過多的研究了,好像是md5二代加密算法,反正把明文扔進去,肯定加密成密文就行了。下面再看一下reqpay.jsp頁面:

<%@page language="java" contentType="text/html;charset=gbk"%>
<html>
    <head>
        <title>To YeePay Page
        </title>
    </head>
    <body>
        <form name="yeepay" action='https://www.yeepay.com/app-merchant-proxy/node' method='POST' target="_blank">
            <input type='hidden' name='p0_Cmd'   value='${requestScope.p0_Cmd}'>
            <input type='hidden' name='p1_MerId' value='${requestScope.p1_MerId}'>
            <input type='hidden' name='p2_Order' value='${requestScope.p2_Order}'>
            <input type='hidden' name='p3_Amt'   value='${requestScope.p3_Amt}'>
            <input type='hidden' name='p4_Cur'   value='${requestScope.p4_Cur}'>
            <input type='hidden' name='p5_Pid'   value='${requestScope.p5_Pid}'>
            <input type='hidden' name='p6_Pcat'  value='${requestScope.p6_Pcat}'>
            <input type='hidden' name='p7_Pdesc' value='${requestScope.p7_Pdesc}'>
            <input type='hidden' name='p8_Url'   value='${requestScope.p8_Url}'>
            <input type='hidden' name='p9_SAF'   value='${requestScope.p9_SAF}'>
            <input type='hidden' name='pa_MP'    value='${requestScope.pa_MP}'>
            <input type='hidden' name='pd_FrpId' value='${requestScope.pd_FrpId}'>
            <input type="hidden" name="pr_NeedResponse"  value="${requestScope.pr_NeedResponse}">
            <input type='hidden' name='hmac' value='${requestScope.hmac}'>
            <input type='submit' />
        </form>
    </body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

  其實該頁面很簡單,就是將明文和密文一起通過<form>表單傳到易寶,易寶的接收url爲https://www.yeepay.com/app-merchant-proxy/node,這也是易寶官方提供的,我們寫成這個就可以了。其實就一個submit按鈕,點擊submit按鈕就能將明文和密文提交過去了。我們看一下測試結果:

3. 測試支付結果

  簡陋的測試前臺index.jsp~~~:
簡陋的測試前臺頁面  
  提交後會到reqpay,jsp,點擊提交按鈕後的效果如下,我們將工行和建行都測一下:
工行支付頁面
建行支付頁面
  支付流程都沒啥問題,本來準備去工行交個1分錢看一下支付完成後的結果,結果發現U盾過期了,因爲現在用支付寶比較方便嘛……就沒去更新U盾了,但是我開通過工行的e支付,所以上面那個界面中也可以使用e支付,於是我就很大方的付了1分錢~~結果如下:
支付成功後
  然後會跳轉到我們之前指定的頁面,也就是同濟大學咯……好了,測試完成了,整個支付流程結束!
  這一節主要是通過一個簡單的demo測試一下,看能否和銀行的支付界面接上,現在測試是沒問題的,已經接上了,後面只要照常支付即可。簡單的demo就介紹到這吧,後面就真正繼續我們之前的網上商城項目的在線支付模塊的開發了。
  
  相關閱讀:http://blog.csdn.net/column/details/str2hiberspring.html
  整個項目的源碼下載地址:http://blog.csdn.net/eson_15/article/details/51479994


—–樂於分享,共同進步!
—–更多文章請看:http://blog.csdn.net/eson_15

(function () { ('pre.prettyprint code').each(function () { var lines = (this).text().split(\n).length;var numbering = $('
    ').addClass('pre-numbering').hide(); (this).addClass(hasnumbering).parent().append( numbering); for (i = 1; i
    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章