大家好,我是梦辛工作室的灵,近期在制作相关软件的过程中,有需要用到对微信商户号的投诉风险合规,第一次搞这个还是踩了不少坑,然后为了方便使用做了以下封装便于大家使用,其实也不难,主要怪自己没有去仔细看微信提供的指导(在 官方提供的 wechatpay-apache-httpclient-0.2.2.jar 的基础上继续简化的),下来就讲下怎么写吧

先准备好以下相关资料:
1.商户号 编号
2.商户号 ApiV3 秘钥,需要在 微信商户号后台设置
3.商户号证书私钥,下载商户号 证书时会有那个 apiclient_key.pem 文件就是它
4.商户号证书序列号,这个可以在 微信商户号证书管理后台查看

准备好以上资料后就可以开始写了
第一步:加载证书,获取到httpCilent对象:

public void setup() {if (!doCheckParam()) {isStep = false;return;}try {privateKey = new String(Files.readAllBytes(Paths.get(privateKeyFilePath)), "utf-8");// 加载商户私钥(privateKey:私钥字符串)merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),apiV3Key.getBytes("utf-8"));// 初始化httpClienthttpClient = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey).withValidator(new WechatPay2Validator(verifier)).build();isStep = true;} catch (Exception e) {// TODO: handle exceptionerrorHint = errorHint.toString();isStep = false;}}

第二步:发起创建投诉回调通知 ,其他操作类同

/*** 创建投诉回调通知* * @param url*            回调地址* @return* @throws Exception*/public String CreateComplaintsNotify(String url) throws Exception {if (!isStep) {errorHint = "未成功启用Step";return null;}String result = null;// 请求URLHttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/merchant-service/complaint-notifications");JSONObject dataJSON = new JSONObject();dataJSON.put("url", url);StringEntity entity = new StringEntity(dataJSON.toString());entity.setContentType("application/json");httpPost.setEntity(entity);httpPost.setHeader("Accept", "application/json");// 完成签名并执行请求CloseableHttpResponse response = httpClient.execute(httpPost);try {int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) { // 处理成功result = EntityUtils.toString(response.getEntity());} else if (statusCode == 204) { // 处理成功,无返回Bodyresult = "{'code':204}";} else {result = EntityUtils.toString(response.getEntity());}} finally {response.close();}return result;}

这里需要注意的是:微信提供的这个 API封装(wechatpay-apache-httpclient-0.2.2.jar)在发起请求时已经自带了 Authorization Header,所以不用再去添加这个Header,不然会报错,楼主在这里被坑了好久,翻了不好文档才找到

还有就是如果有出现 java.security.InvalidKeyException: Illegal key size WechatPayHttpClientBuild 错误的话,请注意 java版本,如果在 java 8u 162 以下的话,请更换 java 版本 ,必须要在 java 8u 162 以上才可以 , 早期的java 运行 限制了 JCE支持的秘钥长度,即默认不支持256位的AES,这里楼主也被坑了好早,试过好多方法才找到,换来是java版本低了

下面是所有代码示例:

package com.mx.util;import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.util.Base64;import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.WechatPayUploadHttpPost;
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.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;import net.sf.json.JSONArray;
import net.sf.json.JSONObject;public class WeChatAPIV3 {CloseableHttpClient httpClient;String mchId = "商户号";String mchSerialNo = "证书序列号";String apiV3Key = "APIV3秘钥";String privateKeyFilePath = "证书私钥路径";String privateKey = "";PrivateKey merchantPrivateKey;String errorHint = "";public String getMchId() {return mchId;}public void setMchId(String mchId) {this.mchId = mchId;}public String getMchSerialNo() {return mchSerialNo;}public void setMchSerialNo(String mchSerialNo) {this.mchSerialNo = mchSerialNo;}public String getApiV3Key() {return apiV3Key;}public void setApiV3Key(String apiV3Key) {this.apiV3Key = apiV3Key;}public String getPrivateKeyFilePath() {return privateKeyFilePath;}public void setPrivateKeyFilePath(String privateKeyFilePath) {this.privateKeyFilePath = privateKeyFilePath;}private boolean isStep = false;public WeChatAPIV3() {}public WeChatAPIV3(String mchId, String mchSerialNo, String apiV3Key, String privateKeyFilePath) {this.mchId = mchId;this.mchSerialNo = mchSerialNo;this.apiV3Key = apiV3Key;this.privateKeyFilePath = privateKeyFilePath;}public String rsaDecryptOAEP(String ciphertext) throws BadPaddingException, IOException {if (!isStep) {errorHint = "未成功启用Step";return null;}if (merchantPrivateKey == null) {errorHint = "未成功加载私钥";return null;}try {Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");cipher.init(Cipher.DECRYPT_MODE, merchantPrivateKey);byte[] data = Base64.getDecoder().decode(ciphertext);return new String(cipher.doFinal(data), "utf-8");} catch (NoSuchPaddingException | NoSuchAlgorithmException e) {throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);} catch (InvalidKeyException e) {throw new IllegalArgumentException("无效的私钥", e);} catch (BadPaddingException | IllegalBlockSizeException e) {throw new BadPaddingException("解密失败");}}public boolean doCheckParam() {return doCheckValue(mchId, mchSerialNo, apiV3Key, privateKeyFilePath);}public boolean doCheckValue(String... item) {for (String each : item) {if (each == null || each.length() == 0) {errorHint = "缺少必要参数";return false;}}return true;}public void setup() {if (!doCheckParam()) {isStep = false;return;}try {privateKey = new String(Files.readAllBytes(Paths.get(privateKeyFilePath)), "utf-8");// 加载商户私钥(privateKey:私钥字符串)merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),apiV3Key.getBytes("utf-8"));// 初始化httpClienthttpClient = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey).withValidator(new WechatPay2Validator(verifier)).build();isStep = true;} catch (Exception e) {// TODO: handle exceptionerrorHint = errorHint.toString();isStep = false;}}/*** 查询投诉详情* * @param complaint_id*            投诉单号* @return* @throws Exception*/public String GetComplaintsInfo(String complaint_id) throws Exception {if (!isStep) {errorHint = "未成功启用Step";return null;}String result = null;// 请求URLHttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/" + complaint_id);httpGet.setHeader("Accept", "application/json");// 完成签名并执行请求CloseableHttpResponse response = httpClient.execute(httpGet);try {int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) { // 处理成功result = EntityUtils.toString(response.getEntity());} else if (statusCode == 204) { // 处理成功,无返回Bodyresult = "{'code':204}";} else {result = EntityUtils.toString(response.getEntity());}} finally {response.close();}return result;}/*** 查询协商历史* * @param complaint_id*            投诉单号* @param offset*            开始位置* @param limit*            返回数据条数* @return* @throws Exception*/public String GetComplaintsHis(String complaint_id, int offset, int limit) throws Exception {if (!isStep) {errorHint = "未成功启用Step";return null;}String result = null;// 请求URLHttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/" + complaint_id+ "/negotiation-historys?limit=" + limit + "&offset=" + offset);httpGet.setHeader("Accept", "application/json");// 完成签名并执行请求CloseableHttpResponse response = httpClient.execute(httpGet);try {int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) { // 处理成功result = EntityUtils.toString(response.getEntity());} else if (statusCode == 204) { // 处理成功,无返回Bodyresult = "{'code':204}";} else {result = EntityUtils.toString(response.getEntity());}} finally {response.close();}return result;}/*** 查询投诉单列表* * @param offset*            开始位置* @param limit*            返回数据条数* @param begin_date*            开始日期 yyyy-MM-dd* @param end_date*            结束日期 yyyy-MM-dd 时间差最大一个月* @return 查询结果* @throws Exception*/public String GetComplaintsList(int offset, int limit, String begin_date, String end_date) throws Exception {if (!isStep) {errorHint = "未成功启用Step";return null;}String result = null;// 请求URLHttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2?limit=" + limit + "&offset=" + offset+ "&begin_date=" + begin_date + "&end_date=" + end_date + "&complainted_mchid=" + mchId);httpGet.setHeader("Accept", "application/json");// 完成签名并执行请求CloseableHttpResponse response = httpClient.execute(httpGet);try {int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) { // 处理成功result = EntityUtils.toString(response.getEntity());} else if (statusCode == 204) { // 处理成功,无返回Bodyresult = "{'code':204}";} else {result = EntityUtils.toString(response.getEntity());}} finally {response.close();}return result;}public void after() throws IOException {httpClient.close();}/*** 创建投诉回调通知* * @param url*            回调地址* @return* @throws Exception*/public String CreateComplaintsNotify(String url) throws Exception {if (!isStep) {errorHint = "未成功启用Step";return null;}String result = null;// 请求URLHttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/merchant-service/complaint-notifications");JSONObject dataJSON = new JSONObject();dataJSON.put("url", url);StringEntity entity = new StringEntity(dataJSON.toString());entity.setContentType("application/json");httpPost.setEntity(entity);httpPost.setHeader("Accept", "application/json");// 完成签名并执行请求CloseableHttpResponse response = httpClient.execute(httpPost);try {int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) { // 处理成功result = EntityUtils.toString(response.getEntity());} else if (statusCode == 204) { // 处理成功,无返回Bodyresult = "{'code':204}";} else {result = EntityUtils.toString(response.getEntity());}} finally {response.close();}return result;}/*** 更新投诉回调通知* * @param url*            回调通知* @return* @throws Exception*/public String UpdateComplaintsNotify(String url) throws Exception {if (!isStep) {errorHint = "未成功启用Step";return null;}String result = null;// 请求URLHttpPut httpPut = new HttpPut("https://api.mch.weixin.qq.com/v3/merchant-service/complaint-notifications");JSONObject dataJSON = new JSONObject();dataJSON.put("url", url);StringEntity entity = new StringEntity(dataJSON.toString());entity.setContentType("application/json");httpPut.setEntity(entity);httpPut.setHeader("Accept", "application/json");// 完成签名并执行请求CloseableHttpResponse response = httpClient.execute(httpPut);try {int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) { // 处理成功result = EntityUtils.toString(response.getEntity());} else if (statusCode == 204) { // 处理成功,无返回Bodyresult = "{'code':204}";} else {result = EntityUtils.toString(response.getEntity());}} finally {response.close();}return result;}/*** 删除投诉回调通知地址* * @return* @throws Exception*/public String DelComplaintsNotify() throws Exception {if (!isStep) {errorHint = "未成功启用Step";return null;}String result = null;// 请求URLHttpDelete httpDel = new HttpDelete("https://api.mch.weixin.qq.com/v3/merchant-service/complaint-notifications");httpDel.setHeader("Accept", "application/json");// 完成签名并执行请求CloseableHttpResponse response = httpClient.execute(httpDel);try {int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) { // 处理成功result = EntityUtils.toString(response.getEntity());} else if (statusCode == 204) { // 处理成功,无返回Bodyresult = "{'code':204}";} else {result = EntityUtils.toString(response.getEntity());}} finally {response.close();}return result;}/*** 提交回复* * @param complaint_id*            被投诉单号* @param response_content*            回复内容* @param response_images*            回复图片* @return* @throws Exception*/public String ReplyInfo(String complaint_id, String response_content, String response_images) throws Exception {if (!isStep) {errorHint = "未成功启用Step";return null;}String result = null;// 请求URLHttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/" + complaint_id + "/response");// 请求body参数JSONObject dataJSON = new JSONObject();dataJSON.put("complainted_mchid", mchId);dataJSON.put("response_content", response_content);String[] imgs = response_images.split(",");JSONArray array = new JSONArray();for (String img : imgs) {array.add(img);}StringEntity entity = new StringEntity(dataJSON.toString());entity.setContentType("application/json");httpPost.setEntity(entity);httpPost.setHeader("Accept", "application/json");// 完成签名并执行请求CloseableHttpResponse response = httpClient.execute(httpPost);try {int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) { // 处理成功result = EntityUtils.toString(response.getEntity());} else if (statusCode == 204) { // 处理成功,无返回Bodyresult = "{'code':204}";} else {result = EntityUtils.toString(response.getEntity());}} finally {response.close();}return result;}/*** 反馈处理完成* * @param complaint_id*            投诉单号* @return* @throws Exception*/public String CompleteComplaints(String complaint_id) throws Exception {if (!isStep) {errorHint = "未成功启用Step";return null;}String result = null;// 请求URLHttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/" + complaint_id + "/complete");JSONObject dataJSON = new JSONObject();dataJSON.put("complainted_mchid", mchId);StringEntity entity = new StringEntity(dataJSON.toString());entity.setContentType("application/json");httpPost.setEntity(entity);httpPost.setHeader("Accept", "application/json");// 完成签名并执行请求CloseableHttpResponse response = httpClient.execute(httpPost);try {int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) { // 处理成功result = EntityUtils.toString(response.getEntity());} else if (statusCode == 204) { // 处理成功,无返回Bodyresult = "{'code':204}";} else {result = EntityUtils.toString(response.getEntity());}} finally {response.close();}return result;}/*** 上传图片* * @param filePath*            图片路径* @return* @throws Exception*/public String uploadImg(String filePath) throws Exception {if (!isStep) {errorHint = "未成功启用Step";return null;}String result = null;URI uri = new URI("https://api.mch.weixin.qq.com/v3/merchant-service/images/upload");File file = new File(filePath);try (FileInputStream ins1 = new FileInputStream(file)) {String sha256 = DigestUtils.sha256Hex(ins1);try (InputStream ins2 = new FileInputStream(file)) {HttpPost request = new WechatPayUploadHttpPost.Builder(uri).withImage(file.getName(), sha256, ins2).build();CloseableHttpResponse response = httpClient.execute(request);try {int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) { // 处理成功result = EntityUtils.toString(response.getEntity());} else if (statusCode == 204) { // 处理成功,无返回Bodyresult = "{'code':204}";} else {result = EntityUtils.toString(response.getEntity());}} finally {response.close();}}}return result;}}

还有一个解密的封装类:

package com.mx.util;import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;public class AesUtil {static final int KEY_LENGTH_BYTE = 32;static final int TAG_LENGTH_BIT = 128;private final byte[] aesKey;/*** 创建解密类* * @param key*/public AesUtil(byte[] key) {if (key.length != KEY_LENGTH_BYTE) {throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");}this.aesKey = key;}/*** 解密数据* * @param associatedData*            附加数据包* @param nonce*            加密使用的随机串初始化向量* @param ciphertext*            Base64编码后的密文* @return* @throws GeneralSecurityException* @throws IOException*/public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)throws GeneralSecurityException, IOException {try {Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");SecretKeySpec key = new SecretKeySpec(aesKey, "AES");GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);cipher.init(Cipher.DECRYPT_MODE, key, spec);cipher.updateAAD(associatedData);return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {throw new IllegalStateException(e);} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {throw new IllegalArgumentException(e);}}
}

上面这个就是楼主封装好的API,简单易用,下面是怎么调用:

public static void main(String[] args) {WeChatAPIV3 apiv3 = new WeChatAPIV3();try {apiv3.setMchId("商户号");apiv3.setMchSerialNo("证书序列号");apiv3.setApiV3Key("APIV3秘钥");apiv3.setPrivateKeyFilePath("证书私钥路径");apiv3.setup();// 查询投诉列表// System.out.print(apiv3.GetComplaintsList(从第几个开始, 返回多少条数据,// "开始日期格式:yyyy-MM-dd","结束日期格式:yyyy-MM-dd"));// 查询投诉详情// System.out.print(apiv3.GetComplaintsInfo("投诉单号"));// 查询投诉历史// System.out.print(apiv3.GetComplaintsHis("投诉单号", 从第几个开始,// 返回多少条数据));// 创建投诉回调通知// System.out.print(apiv3.CreateComplaintsNotify("通知地址"));// 更新投诉回调通知// System.out.print(apiv3.UpdateComplaintsNotify("通知地址"));// 解密电话// System.out.print(apiv3.rsaDecryptOAEP(// "密文"));} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}

你就只用填写对应的参数后,调用上面的方法即可,是不是一下就简单了很多,哇,你们不知道,我当时憋了好久才把这个给憋通,-=- ,主要怪楼主自己没有认真看API文档

github 传送门:https://github.com/wintton/MxWxPayUtil.git
路径:src/com/mx/util/WeChatAPIV3.java

微信支付V3之投诉回调API封装相关推荐

  1. 微信支付V3版本的 签名生成,验签,解密,统一下单的简单封装

    微信支付 V3版本的 签名生成,验签,解密,统一下单的简单封装 V3Base 获取平台证书 回调报文解密 统一下单 暂时看了文档只完成了这部分,感觉封装的不是特别完美,希望有大佬指点一下,想着封装好一 ...

  2. java微信支付v3系列——6.微信支付查询订单API

    目录 java微信支付v3系列--1.微信支付准备工作 java微信支付v3系列--2.微信支付基本配置 java微信支付v3系列--3.订单创建准备操作 java微信支付v3系列--4.创建订单的封 ...

  3. java微信支付v3系列——4.创建订单的封装及使用

    目录 java微信支付v3系列--1.微信支付准备工作 java微信支付v3系列--2.微信支付基本配置 java微信支付v3系列--3.订单创建准备操作 java微信支付v3系列--4.创建订单的封 ...

  4. java微信支付v3系列——8.微信支付之退款成功回调

    目录 java微信支付v3系列--1.微信支付准备工作 java微信支付v3系列--2.微信支付基本配置 java微信支付v3系列--3.订单创建准备操作 java微信支付v3系列--4.创建订单的封 ...

  5. java微信支付v3系列——5.微信支付成功回调

    目录 java微信支付v3系列--1.微信支付准备工作 java微信支付v3系列--2.微信支付基本配置 java微信支付v3系列--3.订单创建准备操作 java微信支付v3系列--4.创建订单的封 ...

  6. Java中的微信支付(2):API V3 微信平台证书的获取与刷新

    1. 前言 在Java 中的微信支付(1):API V3 版本签名详解一文中胖哥讲解了微信支付 V3 版本 API 的签名,当我方(你自己的服务器)请求微信支付服务器时需要根据我方的API 证书对参数 ...

  7. Java中的微信支付(1):API V3版本签名详解

    1. 前言 最近在折腾微信支付,证书还是比较烦人的,所以有必要分享一些经验,减少你在开发微信支付时的踩坑.目前微信支付的 API 已经发展到V3版本,采用了流行的 Restful 风格. 微信支付V2 ...

  8. 微信支付服务器验证的java_Java中的微信支付(3):API V3对微信服务器响应进行签名验证...

    1. 前言 牢记一句话:公钥加密,私钥解密:私钥加签,公钥验签. 微信支付V3版本前两篇分别讲了如何对请求做签名和如何获取并刷新微信平台公钥,本篇将继续展开如何对微信支付响应结果的验签. 2. 为什么 ...

  9. ansible 建 kubernetes 证书签名请求_Java中的微信支付(2):API V3 微信平台证书的获取与刷新...

    1. 前言 在Java 中的微信支付(1):API V3 版本签名详解一文中胖哥讲解了微信支付 V3 版本 API 的签名,当我方(你自己的服务器)请求微信支付服务器时需要根据我方的API 证书对参数 ...

  10. java读取微信证书_Java中的微信支付(2):API V3 微信平台证书的获取与刷新

    1. 前言 在Java中的微信支付(1):API V3版本签名详解一文中胖哥讲解了微信支付V3版本API的签名,当我方(你自己的服务器)请求微信支付服务器时需要根据我方的API证书对参数进行加签,微信 ...

最新文章

  1. Spring MVC 学习笔记 对locale和theme的支持
  2. Windows上安装AD域控制器注意事项及常见问题处理办法
  3. 输入几个数字,用逗号隔开系列例题
  4. 西文是指什么_儒家君子文化:何为君子?为什么要做君子,而不做小人?
  5. 【数据结构与算法】二叉树遍历
  6. python写ui自动化测试用例_自动化测试(6) | Web UI 自动化测试方案
  7. POJ 1753 Flip Game 简单BFS
  8. 金蝶盘点机PDA轻松扫码产品入库,生产型企业进销存条码管理软件
  9. 200行Python代码实现B站UP主小助手(显示视频播放数、粉丝数等)
  10. Windows 开机自动拨号上网
  11. 找不到服务器或应用项目,版本找不到服务器应用程序、源文件或项目的解决办法...
  12. win10卸载软件_win10系统卸载软件超详细教程
  13. 如何创建对电脑屏幕的自动点击的脚本
  14. 计算机毕业设计Java互联网校园家教兼职平台(源码+系统+mysql数据库+lw文档)
  15. Ubuntu安装ClickHouse
  16. Linkedin被“豪吞”,是互联网时代垄断者的网络效应
  17. http 405 错误
  18. warning: NUL character seen; rest of line ignored *** missing separator. Stop.
  19. PCA主成分分析 R语言
  20. 微信小程序实现录音及本地录音播放功能

热门文章

  1. uniapp 滑动操作
  2. slt mysql_SAP SLT操作手册 PDF 下载
  3. Nginx基础之错误页面配置/流量控制/访问控制/变量/监控/HTTPS配置/性能优化
  4. 基于MATLAB中的GUI设计的钢琴界面设计并能发声
  5. CKEditor 4.12.1富文本编辑器的配置与使用(详细版)
  6. Swiper的安装及使用
  7. Google 的浏览器安全手册
  8. 程序员代码面试指南刷题--第四章.打气球的最大分数
  9. js操作html节点中怎么删除子节点,在JavaScript中删除DOM节点的所有子元素
  10. Python识别平台登录验证码