IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 小程序微信支付API V3对接(Java) -> 正文阅读

[移动开发]小程序微信支付API V3对接(Java)


前言

新开发的小程序要调起微信支付,关于微信支付API V3对接(Java)的资料不是很多,研究了很久文档和SDK里的代码,也踩了很多坑,特此记录。
本文包括JSAPI下单、查询订单、关闭订单、小程序调起支付、支付结果通知、申请退款、退款结果通知。


提示:以下是本篇文章正文内容,下面案例可供参考

一、微信支付商户平台

微信支付商户平台—API安全
在【API安全】里需要申请API证书(后需要用的私钥就是这个红框里的)、设置APIv3密钥
在这里插入图片描述

微信支付商户平台—开发配置、AppID账号管理
在【开发配置】里需要配置合法域名
在【AppID账号管理】里需要绑定小程序的AppID

二、代码

1.小程序调起支付

timeStamp:时间戳(只需要到秒,如果获取的是毫秒级需要除以1000)
nonceStr:随机字符串(不长于32位)
package:订单详情扩展字符串(prepay_id=……)
signType:签名方式(仅支持RSA)
paySign:签名(使用字段appId、timeStamp、nonceStr、package计算得出的签名值)

wx.requestPayment({
  timeStamp: '',
  nonceStr: '',
  package: '',
  signType: '',
  paySign: '',
  success: (result) => {},
  fail: () => {},
  complete: () => {}
});

2.后端代码

Maven依赖:

<dependencies>
	<dependency>
		<groupId>commons-httpclient</groupId>
		<artifactId>commons-httpclient</artifactId>
		<version>3.1</version>	
	</dependency>
	<dependency>
		<groupId>org.bouncycastle</groupId>
		<artifactId>bcprov-jdk16</artifactId>
		<version>1.46</version>
	</dependency>
	<dependency>
		<groupId>commons-codec</groupId>
		<artifactId>commons-codec</artifactId>
		<version>1.9</version>
	</dependency>
	<dependency>
		<groupId>commons-io</groupId>
		<artifactId>commons-io</artifactId>
		<version>2.4</version>
	</dependency>
	<dependency>
		<groupId>net.sf.json-lib</groupId>
		<artifactId>json-lib</artifactId>
		<version>2.4</version>
		<classifier>jdk15</classifier>
	</dependency>
	<dependency>
		<groupId>com.github.wechatpay-apiv3</groupId>
		<artifactId>wechatpay-apache-httpclient</artifactId>
		<version>0.4.7</version>
	</dependency>
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-databind</artifactId>
		<version>2.12.5</version>
	</dependency>
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-core</artifactId>
		<version>2.12.5</version>
	</dependency>
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-annotations</artifactId>
		<version>2.12.5</version>
	</dependency>
	<dependency>
		<groupId>cn.hutool</groupId>
		<artifactId>hutool-all</artifactId>
		<version>5.3.10</version>
	</dependency>
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-simple</artifactId>
		<version>1.7.25</version>
	</dependency>
</dependencies>

PayUtil支付工具类:

package util;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.ParseException;
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.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;

import cn.hutool.core.util.RandomUtil;

public class PayUtil {
	public static String createOrder(String openid, String description, int money) {
		Connection conn = DBUtil.getConnection();
		try {
			//唯一的内部订单号(后续支付、关单、查单、退款都跟这个相关)
			String outTradeNo = stringToMD5(System.currentTimeMillis() + "" + RandomUtil.randomString(32));
//			如果用字符串存储私钥就用这个
//			PrivateKey merchantPrivateKey = PemUtil
//					.loadPrivateKey(new ByteArrayInputStream(PayConstants.PRIVATE_KEY.getBytes("utf-8")));
			PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream("私钥文件路径");
			// 获取证书管理器实例
			CertificatesManager certificatesManager = CertificatesManager.getInstance();
			// 向证书管理器增加需要自动更新平台证书的商户信息
			certificatesManager.putMerchant(PayConstants.MCH_ID,
					new WechatPay2Credentials(PayConstants.MCH_ID,
							new PrivateKeySigner(PayConstants.MCH_SERIAL_NO, merchantPrivateKey)),
					PayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));

			CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
					.withMerchant(PayConstants.MCH_ID, PayConstants.MCH_SERIAL_NO, merchantPrivateKey)
					.withValidator(new WechatPay2Validator(certificatesManager.getVerifier(PayConstants.MCH_ID)))
					.build();

			HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
			httpPost.addHeader("Accept", "application/json");
			httpPost.addHeader("Content-type", "application/json; charset=utf-8");

			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectMapper objectMapper = new ObjectMapper();

			ObjectNode rootNode = objectMapper.createObjectNode();
			rootNode.put("mchid", PayConstants.MCH_ID)
					.put("appid", PayConstants.APP_ID)
					.put("description", description)
					.put("notify_url", PayConstants.NOTIFY_URL)
					.put("out_trade_no", outTradeNo);
			rootNode.putObject("amount")
					.put("total", money);
			rootNode.putObject("payer")
					.put("openid", openid);

			objectMapper.writeValue(bos, rootNode);

			httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
			CloseableHttpResponse response = httpClient.execute(httpPost);

			String bodyAsString = EntityUtils.toString(response.getEntity());

			String timestamp = (System.currentTimeMillis() / 1000) + "";
			String nonce = RandomUtil.randomString(32).toUpperCase();
			StringBuilder builder = new StringBuilder();
			builder.append(PayConstants.APP_ID).append("\n");
			builder.append(timestamp).append("\n");
			builder.append(nonce).append("\n");
			JsonNode node = objectMapper.readTree(bodyAsString);
			builder.append("prepay_id="
					+ node.get("prepay_id").toString().substring(1, node.get("prepay_id").toString().length() - 1))
					.append("\n");
			String ciphertext = sign(builder.toString().getBytes(StandardCharsets.UTF_8));

			Map<String, String> map = new HashMap<String, String>();
			map.put("nonceStr", nonce);
			map.put("package", "prepay_id="
					+ node.get("prepay_id").toString().substring(1, node.get("prepay_id").toString().length() - 1));
			map.put("timeStamp", timestamp);
			map.put("paySign", ciphertext);
			map.put("signType", "RSA");
			map.put("outTradeNo", outTradeNo);
			String json = JSON.toJSONString(map);
			//小程序前端获取pay就可以使用wx.requestPayment了
			return "{\"pay\":" + json + "}";
		} catch (UnsupportedCharsetException | HttpCodeException | NotFoundException | ParseException | IOException
				| GeneralSecurityException | SQLException e) {
			e.printStackTrace();
		}
		return "创建订单失败";
	}
	/*支付通知和退款通知给服务器的回调 请求头验签*/
	public static boolean signVerify(String serialNumber, String message, String signature) {
		try {
			PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream("私钥文件路径");
			// 获取证书管理器实例
			CertificatesManager certificatesManager = CertificatesManager.getInstance();
			// 向证书管理器增加需要自动更新平台证书的商户信息
			certificatesManager.putMerchant(PayConstants.MCH_ID,
					new WechatPay2Credentials(PayConstants.MCH_ID,
							new PrivateKeySigner(PayConstants.MCH_SERIAL_NO, merchantPrivateKey)),
					PayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
			// 从证书管理器中获取verifier
			Verifier verifier = certificatesManager.getVerifier(PayConstants.MCH_ID);
			return verifier.verify(serialNumber, message.getBytes(StandardCharsets.UTF_8), signature);
		} catch (HttpCodeException | NotFoundException | IOException | GeneralSecurityException e) {
			e.printStackTrace();
		}
		return false;
	}
	/*支付通知和退款通知给服务器的回调 解密报文*/
	public static String decryptOrder(String body) {
		try {
			AesUtil util = new AesUtil(PayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
			ObjectMapper objectMapper = new ObjectMapper();
			JsonNode node = objectMapper.readTree(body);
			JsonNode resource = node.get("resource");
			String ciphertext = resource.get("ciphertext").textValue();
			String associatedData = resource.get("associated_data").textValue();
			String nonce = resource.get("nonce").textValue();
			return util.decryptToString(associatedData.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext);
		} catch (JsonProcessingException | UnsupportedEncodingException | GeneralSecurityException e) {
			e.printStackTrace();
		}
		return null;
	}
	/*关单*/
	public static void closeOrder(String outTradeNo) {
		try {
			PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream("私钥文件路径");
			// 获取证书管理器实例
			CertificatesManager certificatesManager = CertificatesManager.getInstance();
			// 向证书管理器增加需要自动更新平台证书的商户信息
			certificatesManager.putMerchant(PayConstants.MCH_ID,
					new WechatPay2Credentials(PayConstants.MCH_ID,
							new PrivateKeySigner(PayConstants.MCH_SERIAL_NO, merchantPrivateKey)),
					PayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
		
			CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
					.withMerchant(PayConstants.MCH_ID, PayConstants.MCH_SERIAL_NO, merchantPrivateKey)
					.withValidator(new WechatPay2Validator(certificatesManager.getVerifier(PayConstants.MCH_ID)))
					.build();

			HttpPost httpPost = new HttpPost(
					"https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo + "/close");
			httpPost.addHeader("Accept", "application/json");
			httpPost.addHeader("Content-type", "application/json; charset=utf-8");

			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectMapper objectMapper = new ObjectMapper();

			ObjectNode rootNode = objectMapper.createObjectNode();
			rootNode.put("mchid", PayConstants.MCH_ID);

			objectMapper.writeValue(bos, rootNode);

			httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
			CloseableHttpResponse response = httpClient.execute(httpPost);
			System.out.println(response.getStatusLine().getStatusCode()); // 状态码204就关单成功,无响应内容
		} catch (UnsupportedCharsetException | HttpCodeException | NotFoundException | ParseException | IOException
				| GeneralSecurityException e) {
			e.printStackTrace();
		}
	}
	/*查单*/
	public static String queryOrder(String outTradeNo) {
		try {
			PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream("私钥文件路径");
			// 获取证书管理器实例
			CertificatesManager certificatesManager = CertificatesManager.getInstance();
			// 向证书管理器增加需要自动更新平台证书的商户信息
			certificatesManager.putMerchant(PayConstants.MCH_ID,
					new WechatPay2Credentials(PayConstants.MCH_ID,
							new PrivateKeySigner(PayConstants.MCH_SERIAL_NO, merchantPrivateKey)),
					PayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
			
			CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
					.withMerchant(PayConstants.MCH_ID, PayConstants.MCH_SERIAL_NO, merchantPrivateKey)
					.withValidator(new WechatPay2Validator(certificatesManager.getVerifier(PayConstants.MCH_ID)))
					.build();
			URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"
					+ outTradeNo + "?mchid=" + PayConstants.MCH_ID);
			HttpGet httpGet = new HttpGet(uriBuilder.build());
			httpGet.addHeader("Accept", "application/json");

			CloseableHttpResponse response = httpClient.execute(httpGet);

			String bodyAsString = EntityUtils.toString(response.getEntity());
			System.out.println(bodyAsString);
		} catch (HttpCodeException | NotFoundException | ParseException | IOException | GeneralSecurityException
				| URISyntaxException e) {
			e.printStackTrace();
		}
		return null;
	}
	/*退款*/
	public static String createRefundOrder(String outTradeNo, int total, int refund) {
		try {
			PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream("私钥文件路径");
			// 获取证书管理器实例
			CertificatesManager certificatesManager = CertificatesManager.getInstance();
			// 向证书管理器增加需要自动更新平台证书的商户信息
			certificatesManager.putMerchant(PayConstants.MCH_ID,
					new WechatPay2Credentials(PayConstants.MCH_ID,
							new PrivateKeySigner(PayConstants.MCH_SERIAL_NO, merchantPrivateKey)),
					PayConstants.API_V3KEY.getBytes(StandardCharsets.UTF_8));
			
			CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
					.withMerchant(PayConstants.MCH_ID, PayConstants.MCH_SERIAL_NO, merchantPrivateKey)
					.withValidator(new WechatPay2Validator(certificatesManager.getVerifier(PayConstants.MCH_ID)))
					.build();
			HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
			httpPost.addHeader("Accept", "application/json");
			httpPost.addHeader("Content-type", "application/json; charset=utf-8");

			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectMapper objectMapper = new ObjectMapper();

			ObjectNode rootNode = objectMapper.createObjectNode();
			String outRefundNo = stringToMD5("refund"+outTradeNo);
			rootNode.put("out_refund_no", outRefundNo)
					.put("notify_url", PayConstants.REFUND_NOTIFY_URL)
					.put("out_trade_no", outTradeNo);
			rootNode.putObject("amount")
					.put("total", total)
					.put("refund", refund)
					.put("currency", "CNY");
			objectMapper.writeValue(bos, rootNode);
			httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
			CloseableHttpResponse response = httpClient.execute(httpPost);

			String bodyAsString = EntityUtils.toString(response.getEntity());
			System.out.println(bodyAsString);
		} catch (UnsupportedCharsetException | HttpCodeException | NotFoundException | ParseException | IOException
				| GeneralSecurityException e) {
			e.printStackTrace();
		}
		
		return null;
	}
	/*message为appId、timeStamp、nonceStr、package拼接成的字符串 计算得出paySign*/
	static String sign(byte[] message) {
		try {
			PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(
					PayUtil.class.getClassLoader().getResource("util/apiclient_key.pem").getPath()));
			Signature sign = Signature.getInstance("SHA256withRSA");
			sign.initSign(merchantPrivateKey);
			sign.update(message);
			return Base64.getEncoder().encodeToString(sign.sign());
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (SignatureException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static String stringToMD5(String plainText) {
		byte[] secretBytes = null;
		try {
			secretBytes = MessageDigest.getInstance("md5").digest(plainText.getBytes());
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException("Secret Failed");
		}
		String md5code = new BigInteger(1, secretBytes).toString(16);
		for (int i = 0; i < 32 - md5code.length(); i++) {
			md5code = "0" + md5code;
		}
		return md5code;
	}
}

3.异步通知【回调】(以支付回调为例)

这部分是前端付款成功后微信主动访问你的服务器回调地址给你一个通知

package web;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;

import util.PayUtil;

@WebServlet("/PayCallback")
public class PayCallback extends HttpServlet {
	private static final long serialVersionUID = 1L;

	public PayCallback() {
		super();
	}
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		try {			
			System.out.println("Wechatpay-Timestamp:" + request.getHeader("Wechatpay-Timestamp"));
			System.out.println("Wechatpay-Nonce:" + request.getHeader("Wechatpay-Nonce"));
			System.out.println("Wechatpay-Signature:" + request.getHeader("Wechatpay-Signature"));
			System.out.println("Wechatpay-Serial:" + request.getHeader("Wechatpay-Serial"));
			
			Map<String, String> result = new HashMap<String, String>();
			result.put("code", "FAIL");
			StringBuilder signStr = new StringBuilder();
			signStr.append(request.getHeader("Wechatpay-Timestamp")).append("\n");
			signStr.append(request.getHeader("Wechatpay-Nonce")).append("\n");
			BufferedReader br = request.getReader();
			String str = null;
			StringBuilder builder = new StringBuilder();
			while ((str = br.readLine()) != null) {
				builder.append(str);
			}
			System.out.println(builder.toString());
			signStr.append(builder.toString()).append("\n");
			//进行验签,确保请求来自微信
			if (!PayUtil.signVerify(request.getHeader("Wechatpay-Serial"), signStr.toString(), request.getHeader("Wechatpay-Signature"))) {
				System.out.println("PayCallback==>>sign error");
				result.put("message", "sign error");
				String json = JSON.toJSONString(result);
				PrintWriter out = response.getWriter();
				out.write(json);
				return;
			}
			System.out.println("PayCallback==>>sign success");
			//解密报文
			String info = PayUtil.decryptOrder(builder.toString());
			System.out.println(info);
			ObjectMapper objectMapper = new ObjectMapper();
			JsonNode node = objectMapper.readTree(info);
			//可以解密出很多参数,具体见[官方文档](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_5.shtml)
			String outTradeNo = node.get("out_trade_no").toString().substring(1, node.get("out_trade_no").toString().length() - 1);
			String bankType = node.get("bank_type").toString().substring(1, node.get("bank_type").toString().length() - 1);
			result.put("code", "SUCCESS");
			String json = JSON.toJSONString(result);
			PrintWriter out = response.getWriter();
			out.write(json);
		} catch (IOException | SQLException e) {
			e.printStackTrace();
		} finally {
			DBUtil.close(conn);
		}
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}
}

总结

几个常用的方法都已经封装在PayUtil工具类中,使用的时候根据官方文档传参即可,也可根据需求进行修改。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-06-01 15:22:00  更:2022-06-01 15:22:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 0:47:39-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码