java-微信小程序支付代码分享

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_39316096/article/details/80242283
按照微信支付的流程来进行:
1.小程序调用后台接口生成预支付订单:
package com.stonedt.controller;


import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;


import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;


import com.alibaba.fastjson.JSON;
import com.stonedt.entity.HosOrder;
import com.stonedt.util.DateKit;
import com.stonedt.util.HttpClientsKit;
import com.stonedt.util.ResultUtil;
import com.stonedt.util.WxUtils;


public class Test {


	// 小程序appid
	public static final String appid = "";
	// app 密钥
	public static final String secret = "";
	// 微信支付的商户id
	public static final String mch_id = "";
	// 微信支付的商户密钥
	public static final String key = "";
	// 支付成功后的服务器回调url
	public static final String notify_url = "";
	// 签名方式,固定值
	public static final String SIGNTYPE = "MD5";
	// 交易类型,小程序支付的固定值为JSAPI
	public static final String trade_type = "JSAPI";
	// 微信统一下单接口地址
	public static final String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
	// 微信统一下单接口地址
	public static final String body = "";
	// 交易类型 : 这里是微信小程序支付
	public static final String JSAPI = "JSAPI";


	/**
	 * 支付接口
	 * 
	 * @param openid  用户唯一标识
	 * @return
	 */
	@RequestMapping(value = "pay", produces = "text/json;charset=UTF-8")
	@ResponseBody
	public String pay(HttpServletRequest request,String openid) {


		// 商户订单号--->订单号唯一  
		//这里用时间戳模拟  商户订单号
		String out_trade_no = System.currentTimeMillis()+"";
		// 生成商户订单类      预支付信息组装    用于存入数据库 
		/**
		 * 这里省略 ....个人生成自己的 订单类存入数据
		 */
		// 生成随机串
		String nonce_str = WxUtils.getRandomStringByLength(32);
		// 获取用户后台IP地址(即使用微信小程序的手机IP)
		String spbill_create_ip = WxUtils.getIpAddr(request);


		// 组装参数,用户生成统一下单接口的签名
		Map<String, String> preParams = new HashMap<String, String>();
		
		//attach可以存储你想要在回调中获取的数据
		String attach = out_trade_no;


		preParams.put("appid", appid);
		preParams.put("attach", attach);
		preParams.put("mch_id", mch_id);
		preParams.put("nonce_str", nonce_str);
		preParams.put("body", body);
		preParams.put("out_trade_no", out_trade_no);
		preParams.put("total_fee", "1");// hospital.getHosPrice()
		preParams.put("spbill_create_ip", spbill_create_ip);// 用户ip
		preParams.put("notify_url", notify_url);
		preParams.put("trade_type", trade_type);
		preParams.put("openid", openid);


		// 参数按照参数名ASCII码从小到大排序拼接 (即key1=value1&key2=value2…)
		Map<String, String> cleanSignArray = WxUtils.cleanSignArray(preParams);
		String preSign = WxUtils.createLinkString(cleanSignArray);


		System.out.println(preSign);
		// MD5运算生成签名,这里是第一次签名,用于调用统一下单接口 --签名时 需要全部转换编码
		System.out.println(WxUtils.sign(preSign, key, "utf-8"));
		String fistSign = WxUtils.sign(preSign, key, "utf-8").toUpperCase(); // 问题商户密钥


		// 拼接统一下单接口使用的xml数据,和params存储的数据一样,并且排列顺序安装a.b.c.d...的从小到大顺序排列  要将上一步生成的签名fistSign一起拼接进去
		String xml = "<xml>" + "<appid><![CDATA[" + appid + "]]></appid>" + "<attach><![CDATA[" + attach
				+ "]]></attach>" + "<body><![CDATA[" + body + "]]></body>" + "<mch_id><![CDATA[" + mch_id
				+ "]]></mch_id>" + "<nonce_str><![CDATA[" + nonce_str + "]]></nonce_str>" + "<notify_url><![CDATA["
				+ notify_url + "]]></notify_url>" + "<openid><![CDATA[" + openid + "]]></openid>"
				+ "<out_trade_no><![CDATA[" + out_trade_no + "]]></out_trade_no>" + "<spbill_create_ip><![CDATA["
				+ spbill_create_ip + "]]></spbill_create_ip>" + "<total_fee><![CDATA[1]]></total_fee>"
				+ "<trade_type><![CDATA[" + JSAPI + "]]></trade_type>" + "<sign><![CDATA[" + fistSign + "]]></sign>"
				+ "</xml>";


		System.out.println("调试模式_统一下单接口 请求XML数据:" + xml);


		// 调用统一下单接口,并接受返回的结果 ---》发送请求时 参数需要转换统一编码
		String result = HttpClientsKit.httpRequest(pay_url, "POST", xml);


		System.out.println("调试模式_统一下单接口 返回XML数据:" + result);


		// 这里解析的是xml数据
		Map returnMap = WxUtils.doXMLParse(result);
		// 返回状态码
		String return_code = (String) returnMap.get("return_code");


		System.out.println("返回状态码" + return_code);
		// 返回给小程序端的结果
		Map<String, Object> respone = new HashMap<String, Object>();
		// 返回给小程序端需要的参数
		Map<String, String> data = new HashMap<String, String>();


		// 有返回结果
		if (return_code.equals("SUCCESS")) {
			// 业务结果
			String result_code = (String) returnMap.get("result_code");
			
			
			if (!"SUCCESS".equals(result_code)) {
				//业务结果为fail
				respone.put("code", 1);
				String err_code_des = (String) returnMap.get("err_code_des");
				respone.put("msg", err_code_des);
			}


			String prepay_id = (String) returnMap.get("prepay_id");
			data.put("appId", appid);
			data.put("timeStamp", System.currentTimeMillis() + "");
			data.put("nonceStr", "m9fil9bt27e49ag1jz54vtxffwci7e08");
			data.put("package", "prepay_id=" + prepay_id);
			data.put("signType", "MD5");


			// 拼接签名需要的参数
			// 参数按照参数名ASCII码从小到大排序拼接 (即key1=value1&key2=value2…)
			Map<String, String> signTemp = WxUtils.cleanSignArray(data);
			// 生成签名字符串
			String tempSign = WxUtils.createLinkString(signTemp);
			// 再次签名,这个签名用于小程序端调用wx.requesetPayment方法
			String resSign = WxUtils.sign(tempSign, key, "utf-8");
			data.put("paySign", resSign);


			respone.put("data", data);
			respone.put("code", 0);
			respone.put("msg", "success");
		} else {
			//返回结果失败
			respone.put("code", 1);
			String return_msg = (String) returnMap.get("return_msg");
			respone.put("msg", return_msg);
		}
		//返回的数据用于小 程序  调起支付  或 展示 错误信息
		return JSON.toJSONString(respone);
	}
}

2.小程序根据后台接口生成预支付订单后返回的数据生成预支付订单:
 
wx.requestPayment({
              timeStamp: e.data.data.timeStamp,
              nonceStr: e.data.data.nonceStr,
              package: e.data.data.package,
              signType: 'MD5',
              paySign: e.data.data.paySign,
              success: function (event) {
                // success     
                console.log(event);

                wx.showToast({
                  title: '支付成功',
                  icon: 'success',
                  duration: 2000
                });
                //处理  业务逻辑
                
              },
              fail: function (error) {
                // fail     
                console.log("支付失败")
                wx.showToast({
                  title: '支付失败',
                  icon: 'none',
                  duration: 2000
                });
              }
            })
3.后台的支付回调:

   
/**
	 * 微信通知后台的接口
	 * 
	 * @param request
	 * @return
	 */
	@RequestMapping(value = "notifi")
	@ResponseBody
	public void notifi(HttpServletRequest request, HttpServletResponse response) {
		/**
		 * 当收到通知进行处理时,应检查对应业务数据的状态,判断该通知是否已经处理过
		 * 涉及查询数据库 这里省略
		 * 直接默认未处理
		 */
		String resXml = "";
		try {
			// 通过ServletInputStream读取http请求传入的数据
			BufferedReader br = new BufferedReader(
					new InputStreamReader((ServletInputStream) request.getInputStream()));
			String temp = null;
			// 获取微信返回的xml
			StringBuffer wxRes = new StringBuffer();
			while ((temp = br.readLine()) != null) {
				wxRes.append(temp);
			}
			br.close();
			// 处理微信返回的xml数据
			String notityXml = wxRes.toString();
			System.out.println("接收到微信的返回值(notityXml):" + notityXml);
			Map map = WxUtils.doXMLParse(notityXml);
			// 判断返回结果
			String returnCode = (String) map.get("return_code");
			System.out.println("微信返回结果returnCode:" + returnCode);
			if ("SUCCESS".equals(returnCode)) {
				// 判断result_code--->交易是否成功需要查看result_code来判断
				String result_code = (String) map.get("result_code");
				System.out.println("微信业务返回结果result_code:" + result_code);
				if ("SUCCESS".equals(result_code)) {
					// 成功
					// 获取签名
					String notitySign = (String) map.get("sign");
					// 去除原sign
					map.remove("sign");

					// 对微信传回的数据进行2次签名验证
					if (WxUtils.verify(WxUtils.createLinkString(map), notitySign, key, "utf-8")) {
						// 修改订单信息 --根据订单号order_id
						/**
						 * 修改 根据out_trade_no(我们自己的订单号-->从微信返回的XML中获取 然后修改数据库的订单记录下面三项作为举例) 
						 * 
						 * 支付时间(pay_time) 
						 * 支付状态(order_status=1,支付成功)
						 * 微信流水号(wexin_serial_num)
						 * 
						 */
						// 获取参数
						// 微信支付订单号
						String wxOrderId = (String) map.get("transaction_id");
						// 商户订单号(我们自己的订单号)
						String out_trade_no = (String) map.get("out_trade_no");
						// 支付时间
						String payTime = (String) map.get("time_end");
						// 生成order类
						HosOrder order = new HosOrder();
						order.setOrderId(out_trade_no);
						order.setPayTime(DateKit.localDateTime2Date(payTime));
						order.setOrderStatus(1);
						order.setWexinSerialNum(wxOrderId);
						// 修改订单信息
						/**
						 * 根据serviceImpl  修改 对应订单数据
						 */

						// 通知微信服务器已经处理成功
						resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
								+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
					} else {
						//签名2次验证失败
						System.out.println("微信签名验证失败------");
						resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
								+ "<return_msg><![CDATA[签名验证失败!]]></return_msg>" + "</xml> ";
					}
				} else {
					//微信业务结果失败	
					String err_code_des = (String) map.get("err_code_des");
					System.out.println("微信业务结果失败,错误信息err_code_des:" + err_code_des);
					resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA["
							+ err_code_des + "]]></return_msg>" + "</xml> ";
				}

			}

			System.out.println("微信支付回调数据结束resXml:" + resXml);
			// 返回结果
			BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
			out.write(resXml.getBytes());
			out.flush();
			out.close();

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();

		}

	}
4.微信小程序支付涉及工具类:
package com.stonedt.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.Security;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
 

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;


/**
 * 微信支付工具类
 * @author spring
 *
 */
public class WxUtils {
	/**
	 * 把字符串最后拼接上 key ,再进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign
	 * @param text  需要签名的字符串
	 * @param key   密钥
	 * @param inpit_charset  编码格式 
	 * @return
	 */
	public static String sign(String text,String key,String inpit_charset) {
		String sign=text+"&key="+key;
		return DigestUtils.md5Hex(getContentBytes(sign, inpit_charset));
	}
	
	/**
	 * 验证签名是否正确
	 * @param text  需要签名的字符串 
	 * @param sign  微信的签名结果    
	 * @param key   密钥
	 * @param input_charset  编码格式 
	 * @return 验证结果
	 */
	public static boolean verify(String text, String sign, String key, String input_charset){     
        if(null==text||null==sign||null==key) {
        	return false;  
        }else {
        	text = text +"&key="+ key;     
            String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset)).toUpperCase();
            System.out.println("打印微信返回的数据组装的新的sign--mysign:"+mysign);
            if (mysign.equals(sign)) {     
                return true;     
            } else {     
                return false;     
            }  
        }
    } 
	
	
		
	/**
	 * 把字符串按照一点编码格式转成字节数组
	 * @param content  字符串
	 * @param charset  编码方式
	 * @return
	 */
	 public static byte[] getContentBytes(String content, String charset) {     
	        if (charset == null || "".equals(charset)) {     
	            return content.getBytes();     
	        }     
	        try {     
	            return content.getBytes(charset);     
	        } catch (UnsupportedEncodingException e) {     
	            throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);     
	        }     
	    } 
	 
	 	
	 	/**   
	     * 除去数组中的空值和签名参数   
	     * @param sArray 签名参数组   
	     * @return 去掉空值与签名参数后的新签名参数组   
	     */     
	    public static Map<String, String> cleanSignArray(Map<String, String> signArray) {     
	        Map<String, String> result = new HashMap<String, String>();     
	        if (signArray == null || signArray.size() <= 0) {     
	            return result;     
	        }     
	        for (String key : signArray.keySet()) {     
	            String value = signArray.get(key);     
	            if (value == null || value.equals("")) {     
	                continue;     
	            }     
	            result.put(key, value);     
	        }     
	        return result;     
	    } 
	 
	    /**   
	     * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串   
	     * @param params 需要排序并参与字符拼接的参数组   
	     * @return 拼接后字符串   
	     */     
	    public static String createLinkString(Map<String, String> params) {     
	        List<String> keys = new ArrayList<String>(params.keySet());     
	        //Collections是一个工具类,sort是其中的静态方法,是用来对List类型进行排序,规则是从小到大
	        Collections.sort(keys);     
	        String prestr = "";     
	        for (int i = 0; i < keys.size(); i++) {     
	            String key = keys.get(i);     
	            String value = params.get(key);     
	            if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符     
	                prestr = prestr + key + "=" + value;     
	            } else {     
	                prestr = prestr + key + "=" + value + "&";     
	            }     
	        }     
	        return prestr;     
	    }  
	    
	    
	    /** 
	     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 
	     * @param strxml 
	     * @return 
	     * @throws JDOMException 
	     * @throws IOException 
	     */  
	    public static Map doXMLParse(String strxml){ 
	    	Map m = new HashMap();  
	    	try {
	        if(null == strxml || "".equals(strxml)) {  
	            return null;  
	        }  
	       
	        InputStream in = String2Inputstream(strxml);  
	        SAXBuilder builder = new SAXBuilder();  
	        Document doc;
			
				doc = builder.build(in);
			 
	        Element root = doc.getRootElement();  
	        List list = root.getChildren();  
	        Iterator it = list.iterator();  
	        while(it.hasNext()) {  
	            Element e = (Element) it.next();  
	            String k = e.getName();  
	            String v = "";  
	            List children = e.getChildren();  
	            if(children.isEmpty()) {  
	                v = e.getTextNormalize();  
	            } else {  
	                v = getChildrenText(children);  
	            }  
	              
	            m.put(k, v);  
	        }  
	          
	        //关闭流  
	        in.close();
	    	} catch (JDOMException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} 
	    	return m; 
	    }  
	    /** 
	     * 获取子结点的xml 
	     * @param children 
	     * @return String 
	     */  
	    public static String getChildrenText(List children) {  
	        StringBuffer sb = new StringBuffer();  
	        if(!children.isEmpty()) {  
	            Iterator it = children.iterator();  
	            while(it.hasNext()) {  
	                Element e = (Element) it.next();  
	                String name = e.getName();  
	                String value = e.getTextNormalize();  
	                List list = e.getChildren();  
	                sb.append("<" + name + ">");  
	                if(!list.isEmpty()) {  
	                    sb.append(getChildrenText(list));  
	                }  
	                sb.append(value);  
	                sb.append("</" + name + ">");  
	            }  
	        }  
	          
	        return sb.toString();  
	    }  
	    
	    public static InputStream String2Inputstream(String str) {  
	        return new ByteArrayInputStream(str.getBytes());  
	    }  
	    
	    
	    /** 
	     * 获取真实的ip地址 
	     * @param request 
	     * @return 
	     */  
	    public static String getIpAddr(HttpServletRequest request) {  
	        String ip = request.getHeader("X-Forwarded-For");  
	        if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){  
	             //多次反向代理后会有多个ip值,第一个ip才是真实ip  
	            int index = ip.indexOf(",");  
	            if(index != -1){  
	                return ip.substring(0,index);  
	            }else{  
	                return ip;  
	            }  
	        }  
	        ip = request.getHeader("X-Real-IP");  
	        if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){  
	           return ip;  
	        }  
	        return request.getRemoteAddr();  
	    }
	    
	    /** 
	     * 获取一定长度的随机字符串,范围0-9,a-z 
	     * @param length:指定字符串长度 
	     * @return 一定长度的随机字符串 
	     */  
	    public static String getRandomStringByLength(int length) {  
	        String base = "abcdefghijklmnopqrstuvwxyz0123456789";  
	        Random random = new Random();  
	        StringBuffer sb = new StringBuffer();  
	        for (int i = 0; i < length; i++) {  
	            int number = random.nextInt(base.length());  
	            sb.append(base.charAt(number));  
	        }  
	        return sb.toString();  
	       } 
	   
	    ////////////////下面是解密手机号的方法
	    
	    // 算法名
	    public static final String KEY_NAME = "AES";
	    // 加解密算法/模式/填充方式
	    // ECB模式只用密钥即可对数据进行加密解密,CBC模式需要添加一个iv
	    public static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";

	    /**
	     * 微信 数据解密<br/>
	     * 对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充
	     * 对称解密的目标密文:encrypted=Base64_Decode(encryptData)
	     * 对称解密秘钥:key = Base64_Decode(session_key),aeskey是16字节
	     * 对称解密算法初始向量:iv = Base64_Decode(iv),同样是16字节
	     *
	     * @param encrypted 目标密文
	     * @param session_key 会话ID
	     * @param iv 加密算法的初始向量
	     */
	    public static String wxDecrypt(String encrypted, String session_key, String iv) {
	        String json = null;
	        byte[] encrypted64 = Base64.decodeBase64(encrypted);
	        byte[] key64 = Base64.decodeBase64(session_key);
	        byte[] iv64 = Base64.decodeBase64(iv);
	        byte[] data;
	        try {
	            init();
	            json = new String(decrypt(encrypted64, key64, generateIV(iv64)));
	        } catch (Exception e) {
	            e.printStackTrace();
	        }
	        return json;
	    }

	    /**
	     * 初始化密钥
	     */
	    public static void init() throws Exception {
	        Security.addProvider(new BouncyCastleProvider());
	        KeyGenerator.getInstance(KEY_NAME).init(128);
	    }

	    /**
	     * 生成iv
	     */
	    public static AlgorithmParameters generateIV(byte[] iv) throws Exception {
	        // iv 为一个 16 字节的数组,这里采用和 iOS 端一样的构造方法,数据全为0
	        // Arrays.fill(iv, (byte) 0x00);
	        AlgorithmParameters params = AlgorithmParameters.getInstance(KEY_NAME);
	        params.init(new IvParameterSpec(iv));
	        return params;
	    }

	    /**
	     * 生成解密
	     */
	    public static byte[] decrypt(byte[] encryptedData, byte[] keyBytes, AlgorithmParameters iv)
	            throws Exception {
	        Key key = new SecretKeySpec(keyBytes, KEY_NAME);
	        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
	        // 设置为解密模式
	        cipher.init(Cipher.DECRYPT_MODE, key, iv);
	        return cipher.doFinal(encryptedData);
	    }
}

微信返回的时间的格式问题(yyyyMMddHHmmss类型)

 /** 
     * yyyyMMddHHmmss 转换为Date 
     * @param localDateTime 
     */  
	public static Date localDateTime2Date( String datetime){ 
		//先把符合格式的字符串时间  转换成LocalDateTime
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");  
        LocalDateTime ldt = LocalDateTime.parse(datetime,dtf);  
        //把LocalDateTime 转换成Date
        ZoneId zoneId = ZoneId.systemDefault();  
        ZonedDateTime zdt = ldt.atZone(zoneId);//Combines this date-time with a time-zone to create a  ZonedDateTime.  
        Date date = Date.from(zdt.toInstant());  
        
        return date;
    }  

发送和接收 统一下单接口内容

 

/**   
     *   
     * @param requestUrl 请求地址   
     * @param requestMethod 请求方法   
     * @param outputStr 参数   
     */     
    public static String httpRequest(String requestUrl,String requestMethod,String outputStr){     
          
        StringBuffer buffer = null;     
        try{     
            URL url = new URL(requestUrl);     
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();     
            conn.setRequestMethod(requestMethod); 
            conn.setRequestProperty("Content-Type","text/xml;charset=UTF-8");
            conn.setDoOutput(true);     
            conn.setDoInput(true);     
            conn.connect();     
            //往服务器端写内容     
            if(null !=outputStr){     
                OutputStream os=conn.getOutputStream();     
                os.write(outputStr.getBytes("utf-8"));     
                os.close();     
            }     
            // 读取服务器端返回的内容     
            InputStream is = conn.getInputStream();     
            InputStreamReader isr = new InputStreamReader(is, "utf-8");     
            BufferedReader br = new BufferedReader(isr);     
            buffer = new StringBuffer();     
            String line = null;     
            while ((line = br.readLine()) != null) {     
                buffer.append(line);     
            }     

                br.close();  
        }catch(Exception e){     
            e.printStackTrace();     
        }  
        return buffer.toString();  
    }  

最后感觉一位博主:https://blog.csdn.net/zhourenfei17/article/details/77765585

借鉴了不少,虽然有些地方出现了问题。。。。感谢!

展开阅读全文

没有更多推荐了,返回首页