基于springboot实现小程序微信支付与退款

最近需要再写小程序商城,无可避免的需要用到微信支付与商品售后退款等功能。于是研究了一些大佬的代码之后整合出了这个比较简单的微信支付与退款。

相关内容引用了以下两位大佬的。

https://blog.csdn.net/Majker/article/details/88379695

https://blog.csdn.net/wenqiwenqi123/article/details/78734405

先看看微信支付的流程,然后直接上代码测试。

先说明一些问题,安全证书仅仅是作为退款才有作用,如果仅仅是测试微信支付或者还未申请到安全证书的话,大可将构造器代码注释掉。

1.相关pom文件如下:

<!--wx 登录--><!-- http请求工具包依赖 --><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.2</version></dependency><!--wx支付--><dependency><groupId>com.github.wxpay</groupId><artifactId>wxpay-sdk</artifactId><version>0.0.3</version></dependency>

2.配置类如下

1.HttpKit
package cn.gdpu.config.pay;import javax.net.ssl.*;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.Map;/*** @ClassName HttpKit* @Author ttaurus* @Date Create in 2020/1/21 18:18*/
public class HttpKit{private static final String GET = "GET";private static final String POST = "POST";private static String CHARSET = "UTF-8";private static final SSLSocketFactory sslSocketFactory = initSSLSocketFactory();private static final TrustAnyHostnameVerifier trustAnyHostnameVerifier = new HttpKit().new TrustAnyHostnameVerifier();//    public static final OkHttp3Delegate delegate = new OkHttp3Delegate();private HttpKit() {}private static SSLSocketFactory initSSLSocketFactory() {try {TrustManager[] e = new TrustManager[]{new HttpKit().new TrustAnyTrustManager()};SSLContext sslContext = SSLContext.getInstance("TLS");sslContext.init((KeyManager[])null, e, new SecureRandom());return sslContext.getSocketFactory();} catch (Exception var2) {throw new RuntimeException(var2);}}public static void setCharSet(String charSet) {if(charSet!=null && !charSet.equals("")) {throw new IllegalArgumentException("charSet can not be blank.");} else {CHARSET = charSet;}}private static HttpURLConnection getHttpConnection(String url, String method, Map<String, String> headers) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, KeyManagementException{URL _url = new URL(url);HttpURLConnection conn = (HttpURLConnection)_url.openConnection();if(conn instanceof HttpsURLConnection) {((HttpsURLConnection)conn).setSSLSocketFactory(sslSocketFactory);((HttpsURLConnection)conn).setHostnameVerifier(trustAnyHostnameVerifier);}conn.setRequestMethod(method);conn.setDoOutput(true);conn.setDoInput(true);conn.setConnectTimeout(19000);conn.setReadTimeout(19000);conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");conn.setRequestProperty("AuthUser-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36");if(headers != null && !headers.isEmpty()) {Iterator i$ = headers.entrySet().iterator();while(i$.hasNext()) {Map.Entry entry = (Map.Entry)i$.next();conn.setRequestProperty((String)entry.getKey(), (String)entry.getValue());}}return conn;}public static String get(String url, Map<String, String> queryParas, Map<String, String> headers) {HttpURLConnection conn = null;String e;try {conn = getHttpConnection(buildUrlWithQueryString(url, queryParas), "GET", headers);conn.connect();e = readResponseString(conn);} catch (Exception var8) {throw new RuntimeException(var8);} finally {if(conn != null) {conn.disconnect();}}return e;}public static String get(String url, Map<String, String> queryParas) {return get(url, queryParas, (Map)null);}public static String get(String url) {return get(url, (Map)null, (Map)null);}public static String post(String url, Map<String, String> queryParas, String data, Map<String, String> headers) {HttpURLConnection conn = null;String var6;try {conn = getHttpConnection(buildUrlWithQueryString(url, queryParas), "POST", headers);conn.connect();OutputStream e = conn.getOutputStream();e.write(data.getBytes(CHARSET));e.flush();e.close();var6 = readResponseString(conn);} catch (Exception var10) {throw new RuntimeException(var10);} finally {if(conn != null) {conn.disconnect();}}return var6;}public static String post(String url, Map<String, String> queryParas, String data) {return post(url, queryParas, data, (Map)null);}public static String post(String url, String data, Map<String, String> headers) {return post(url, (Map)null, data, headers);}public static String post(String url, String data) {return post(url, (Map)null, data, (Map)null);}private static String readResponseString(HttpURLConnection conn) {StringBuilder sb = new StringBuilder();InputStream inputStream = null;try {inputStream = conn.getInputStream();BufferedReader e = new BufferedReader(new InputStreamReader(inputStream, CHARSET));String line = null;while((line = e.readLine()) != null) {sb.append(line).append("\n");}String var5 = sb.toString();return var5;} catch (Exception var14) {throw new RuntimeException(var14);} finally {if(inputStream != null) {try {inputStream.close();} catch (IOException var13) {}}}}private static String buildUrlWithQueryString(String url, Map<String, String> queryParas) {if(queryParas != null && !queryParas.isEmpty()) {StringBuilder sb = new StringBuilder(url);boolean isFirst;if(url.indexOf("?") == -1) {isFirst = true;sb.append("?");} else {isFirst = false;}String key;String value;for(Iterator i$ = queryParas.entrySet().iterator() ; i$.hasNext(); sb.append(key).append("=").append(value)) {Map.Entry entry = (Map.Entry)i$.next();if(isFirst) {isFirst = false;} else {sb.append("&");}key = (String)entry.getKey();value = (String)entry.getValue();if(value!=null && !value.equals("")) {try {value = URLEncoder.encode(value, CHARSET);} catch (UnsupportedEncodingException var9) {throw new RuntimeException(var9);}}}return sb.toString();} else {return url;}}public static String readData(HttpServletRequest request) {BufferedReader br = null;try {StringBuilder e = new StringBuilder();br = request.getReader();String line = null;while((line = br.readLine()) != null) {e.append(line).append("\n");}line = e.toString();return line;} catch (IOException var12) {throw new RuntimeException(var12);} finally {if(br != null) {try {br.close();} catch (IOException var11) {}}}}/** @deprecated */@Deprecatedpublic static String readIncommingRequestData(HttpServletRequest request) {return readData(request);}private class TrustAnyTrustManager implements X509TrustManager {private TrustAnyTrustManager() {}public X509Certificate[] getAcceptedIssuers() {return null;}public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException{}public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}}private class TrustAnyHostnameVerifier implements HostnameVerifier {private TrustAnyHostnameVerifier() {}public boolean verify(String hostname, SSLSession session) {return true;}}
}
 2.PayConfig 这个类尤其重要 里面的参数必须都要具备 且不能错误 需要手动填写```java
package cn.gdpu.config.pay;import com.github.wxpay.sdk.WXPayConfig;import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;/*** @ClassName WXPayConfig* @Author ttaurus* @Date Create in 2020/1/21 11:22*/
public class PayConfig implements WXPayConfig{private byte[] certData;/*** 微信退款所需要的配置! 退款只需要证书即可。* @throws Exception*/public PayConfig() throws Exception {//部署服务器用到的路径 绝对路径。//String certPath = "/usr/local/app/tomcat-dev/webapps/shop/WEB-INF/classes/apiclient_cert.p12";//从微信商户平台下载的安全证书存放的目录//本地用到的路径 相对路径String certPath = "src/main/resources/apiclient_cert.p12";File file = new File(certPath);InputStream certStream = new FileInputStream(file);this.certData = new byte[(int) file.length()];certStream.read(this.certData);certStream.close();}@Overridepublic String getAppID() {return "";  //appid}public String getAPPSECRET(){return "";  //appSecret}@Overridepublic String getMchID() {return "";   //商户号id} @Overridepublic String getKey() {return "";  //支付API密钥}public String getNOTIFY_URL(){/*支付回调URL 必须在https下访问。这一步无法在本地实现 必须设置在服务器上此url只是作为一个样例。*/return "https://www.testtest.design21/dev/user/pay/notify"; }@Overridepublic InputStream getCertStream() {ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);return certBis;}@Overridepublic int getHttpConnectTimeoutMs() {return 8000;}@Overridepublic int getHttpReadTimeoutMs() {return 10000;}}
3.PaymentApi 工具类 直接复制即可
package cn.gdpu.config.pay;import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;/*** @ClassName PaymentApi* @Author ttaurus* @Date Create in 2020/1/21 17:34*/
public class PaymentApi{private PaymentApi() {}// 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1private static String unifiedOrderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";public enum TradeType {JSAPI, NATIVE, APP, WAP, MWEB}/*** 统一下单* @param params 参数map* @return String*/public static String pushOrder(Map<String, String> params) {return HttpKit.post(unifiedOrderUrl, PaymentKit.toXml(params));}private static Map<String, String> request(String url, Map<String, String> params, String paternerKey) {params.put("nonce_str", System.currentTimeMillis() + "");String sign = PaymentKit.createSign(params, paternerKey);params.put("sign", sign);String xmlStr = HttpKit.post(url, PaymentKit.toXml(params));return PaymentKit.xmlToMap(xmlStr);}/*** 文档说明:https://pay.weixin.qq.com/wiki/doc/api/wap.php?chapter=15_4* <pre>* @param appId 公众账号ID         是    String(32)    wx8888888888888888    微信分配的公众账号ID* 随机字符串         noncestr    是    String(32)    5K8264ILTKCH16CQ2502SI8ZNMTM67VS    随机字符串,不长于32位。推荐随机数生成算法* 订单详情扩展字符串    package        是    String(32)    WAP    扩展字段,固定填写WAP* @param prepayId 预支付交易会话标识    是    String(64)    wx201410272009395522657a690389285100    微信统一下单接口返回的预支付回话标识,用于后续接口调用中使用,该值有效期为2小时* 签名                 sign        是    String(32)    C380BEC2BFD727A4B6845133519F3AD6    签名,详见签名生成算法* 时间戳            timestamp    是    String(32)    1414561699    当前的时间,其他详见时间戳规则* @param paternerKey 签名密匙* </pre>* @return {String}*/public static String getDeepLink(String appId, String prepayId, String paternerKey) {Map<String, String> params = new HashMap<String, String>();params.put("appid", appId);params.put("noncestr", System.currentTimeMillis() + "");params.put("package", "WAP");params.put("prepayid", prepayId);params.put("timestamp", System.currentTimeMillis() / 1000 + "");String sign = PaymentKit.createSign(params, paternerKey);params.put("sign", sign);String string1 = PaymentKit.packageSign(params, true);String string2 = "";try { string2 = PaymentKit.urlEncode(string1); } catch (UnsupportedEncodingException e) {}return "weixin://wap/pay?" + string2;}// 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2private static String orderQueryUrl = "https://api.mch.weixin.qq.com/pay/orderquery";/*** 根据商户订单号查询信息* @param appid 公众账号ID* @param mch_id 商户号* @param paternerKey 商户密钥* @param transaction_id 微信订单号* @return 回调信息*/public static Map<String, String> queryByTransactionId(String appid, String mch_id, String paternerKey, String transaction_id) {Map<String, String> params = new HashMap<String, String>();params.put("appid", appid);params.put("mch_id", mch_id);params.put("transaction_id", transaction_id);return request(orderQueryUrl, params, paternerKey);}/*** 根据商户订单号查询信息* @param appid 公众账号ID* @param mch_id 商户号* @param paternerKey 商户密钥* @param out_trade_no 商户订单号* @return 回调信息*/public static Map<String, String> queryByOutTradeNo(String appid, String mch_id, String paternerKey, String out_trade_no) {Map<String, String> params = new HashMap<String, String>();params.put("appid", appid);params.put("mch_id", mch_id);params.put("out_trade_no", out_trade_no);return request(orderQueryUrl, params, paternerKey);}// 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3private static String closeOrderUrl = "https://api.mch.weixin.qq.com/pay/closeorder";/*** 关闭订单* @param appid 公众账号ID* @param mch_id 商户号* @param paternerKey 商户密钥* @param out_trade_no 商户订单号* @return 回调信息*/public static Map<String, String> closeOrder(String appid, String mch_id, String paternerKey, String out_trade_no) {Map<String, String> params = new HashMap<String, String>();params.put("appid", appid);params.put("mch_id", mch_id);params.put("out_trade_no", out_trade_no);return request(closeOrderUrl, params, paternerKey);}// 申请退款文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4public static String refundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";// 查询退款文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5private static String refundQueryUrl = "https://api.mch.weixin.qq.com/pay/refundquery";private static Map<String, String> baseRefundQuery(Map<String, String> params, String appid, String mch_id, String paternerKey) {params.put("appid", appid);params.put("mch_id", mch_id);return request(refundQueryUrl, params, paternerKey);}/*** 根据微信订单号查询退款* @param appid 公众账号ID* @param mch_id 商户号* @param paternerKey 商户密钥* @param transaction_id 微信订单号* @return map*/public static Map<String, String> refundQueryByTransactionId(String appid, String mch_id, String paternerKey, String transaction_id) {Map<String, String> params = new HashMap<String, String>();params.put("transaction_id", transaction_id);return baseRefundQuery(params, appid, mch_id, paternerKey);}/*** 根据微信订单号查询退款* @param appid 公众账号ID* @param mch_id 商户号* @param paternerKey 商户密钥* @param out_trade_no 商户订单号* @return map*/public static Map<String, String> refundQueryByOutTradeNo(String appid, String mch_id, String paternerKey, String out_trade_no) {Map<String, String> params = new HashMap<String, String>();params.put("out_trade_no", out_trade_no);return baseRefundQuery(params, appid, mch_id, paternerKey);}/*** 根据微信订单号查询退款* @param appid 公众账号ID* @param mch_id 商户号* @param paternerKey 商户密钥* @param out_refund_no 商户退款单号* @return map*/public static Map<String, String> refundQueryByOutRefundNo(String appid, String mch_id, String paternerKey, String out_refund_no) {Map<String, String> params = new HashMap<String, String>();params.put("out_refund_no", out_refund_no);return baseRefundQuery(params, appid, mch_id, paternerKey);}/*** 根据微信订单号查询退款* @param appid 公众账号ID* @param mch_id 商户号* @param paternerKey 商户密钥* @param refund_id 微信退款单号* @return map*/public static Map<String, String> refundQueryByRefundId(String appid, String mch_id, String paternerKey, String refund_id) {Map<String, String> params = new HashMap<String, String>();params.put("refund_id", refund_id);return baseRefundQuery(params, appid, mch_id, paternerKey);}private static String downloadBillUrl = "https://api.mch.weixin.qq.com/pay/downloadbill";/*** <pre>* ALL,返回当日所有订单信息,默认值* SUCCESS,返回当日成功支付的订单* REFUND,返回当日退款订单* REVOKED,已撤销的订单* </pre>*/public static enum BillType {ALL, SUCCESS, REFUND, REVOKED}/*** 下载对账单* <pre>* 公众账号ID    appid        是    String(32)    wx8888888888888888    微信分配的公众账号ID(企业号corpid即为此appId)* 商户号        mch_id        是    String(32)    1900000109    微信支付分配的商户号* 设备号        device_info    否    String(32)    013467007045764    微信支付分配的终端设备号* 随机字符串    nonce_str    是    String(32)    5K8264ILTKCH16CQ2502SI8ZNMTM67VS    随机字符串,不长于32位。推荐随机数生成算法* 签名        sign        是    String(32)    C380BEC2BFD727A4B6845133519F3AD6    签名,详见签名生成算法* 对账单日期    bill_date    是    String(8)    20140603    下载对账单的日期,格式:20140603* 账单类型        bill_type    否    String(8)* </pre>* @param appid 公众账号ID* @param mch_id 商户号* @param paternerKey 签名密匙* @param billDate 对账单日期* @return String*/public static String downloadBill(String appid, String mch_id, String paternerKey, String billDate) {return downloadBill(appid, mch_id, paternerKey, billDate, null);}/*** 下载对账单* <pre>* 公众账号ID    appid        是    String(32)    wx8888888888888888    微信分配的公众账号ID(企业号corpid即为此appId)* 商户号        mch_id        是    String(32)    1900000109    微信支付分配的商户号* 设备号        device_info    否    String(32)    013467007045764    微信支付分配的终端设备号* 随机字符串    nonce_str    是    String(32)    5K8264ILTKCH16CQ2502SI8ZNMTM67VS    随机字符串,不长于32位。推荐随机数生成算法* 签名        sign        是    String(32)    C380BEC2BFD727A4B6845133519F3AD6    签名,详见签名生成算法* 对账单日期    bill_date    是    String(8)    20140603    下载对账单的日期,格式:20140603* 账单类型        bill_type    否    String(8)* </pre>* @param appid 公众账号ID* @param mch_id 商户号* @param paternerKey 签名密匙* @param billDate 对账单日期* @param billType 账单类型* @return String*/public static String downloadBill(String appid, String mch_id, String paternerKey, String billDate, BillType billType) {Map<String, String> params = new HashMap<String, String>();params.put("appid", appid);params.put("mch_id", mch_id);params.put("nonce_str", System.currentTimeMillis() + "");params.put("bill_date", billDate);if (null != billType) {params.put("bill_type", billType.name());} else {params.put("bill_type", BillType.ALL.name());}String sign = PaymentKit.createSign(params, paternerKey);params.put("sign", sign);return HttpKit.post(downloadBillUrl, PaymentKit.toXml(params));}
}
4.PaymentKit 工具类 可不管直接复制即可
package cn.gdpu.config.pay;import org.apache.commons.codec.Charsets;import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import java.util.TreeMap;/*** @ClassName PaymentKit* @Author ttaurus* @Date Create in 2020/1/21 18:12*/
public class PaymentKit{/*** 组装签名的字段* @param params 参数* @param urlEncoder 是否urlEncoder* @return String*/public static String packageSign(Map<String, String> params, boolean urlEncoder) {// 先将参数以其参数名的字典序升序进行排序TreeMap<String, String> sortedParams = new TreeMap<String, String>(params);// 遍历排序后的字典,将所有参数按"key=value"格式拼接在一起StringBuilder sb = new StringBuilder();boolean first = true;for (Map.Entry<String, String> param : sortedParams.entrySet()) {String value = param.getValue();if (Tools.isEmpty(value)) {continue;}if (first) {first = false;} else {sb.append("&");}sb.append(param.getKey()).append("=");if (urlEncoder) {try { value = urlEncode(value); } catch (UnsupportedEncodingException e) {}}sb.append(value);}return sb.toString();}/*** urlEncode* @param src 微信参数* @return String* @throws UnsupportedEncodingException 编码错误*/public static String urlEncode(String src) throws UnsupportedEncodingException {return URLEncoder.encode(src, Charsets.UTF_8.name()).replace("+", "%20");}/*** 生成签名* @param params 参数* @param paternerKey 支付密钥* @return sign*/public static String createSign(Map<String, String> params, String paternerKey) {// 生成签名前先去除signparams.remove("sign");String stringA = packageSign(params, false);String stringSignTemp = stringA + "&key=" + paternerKey;return Tools.md5(stringSignTemp).toUpperCase();}/*** 支付异步通知时校验sign* @param params 参数* @param paternerKey 支付密钥* @return {boolean}*/public static boolean verifyNotify(Map<String, String> params, String paternerKey){String sign = params.get("sign");String localSign = PaymentKit.createSign(params, paternerKey);return sign.equals(localSign);}/*** 微信下单,map to xml* @param params 参数* @return String*/public static String toXml(Map<String, String> params) {StringBuilder xml = new StringBuilder();xml.append("<xml>");for (Map.Entry<String, String> entry : params.entrySet()) {String key   = entry.getKey();String value = entry.getValue();// 略过空值if (Tools.isEmpty(value)) continue;xml.append("<").append(key).append(">");xml.append(entry.getValue());xml.append("</").append(key).append(">");}xml.append("</xml>");return xml.toString();}/*** 针对支付的xml,没有嵌套节点的简单处理* @param xmlStr xml字符串* @return map集合*/public static Map<String, String> xmlToMap(String xmlStr) {XmlHelper xmlHelper = XmlHelper.of(xmlStr);return xmlHelper.toMap();}}
5.Tools 工具类 直接复制即可
package cn.gdpu.config.pay;import java.security.MessageDigest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** @ClassName Tools* @Author ttaurus* @Date Create in 2020/1/21 18:13*/
public class Tools{/*** 随机生成六位数验证码 * @return*/public static int getRandomNum(){Random r = new Random();return r.nextInt(900000)+100000;//(Math.random()*(999999-100000)+100000)}/*** 检测字符串是否不为空(null,"","null")* @param s* @return 不为空则返回true,否则返回false*/public static boolean notEmpty(String s){return s!=null && !"".equals(s) && !"null".equals(s);}/*** 检测字符串是否为空(null,"","null")* @param s* @return 为空则返回true,不否则返回false*/public static boolean isEmpty(String s){return s==null || "".equals(s) || "null".equals(s);}/*** 字符串转换为字符串数组* @param str 字符串* @param splitRegex 分隔符* @return*/public static String[] str2StrArray(String str,String splitRegex){if(isEmpty(str)){return null;}return str.split(splitRegex);}/*** 用默认的分隔符(,)将字符串转换为字符串数组* @param str    字符串* @return*/public static String[] str2StrArray(String str){return str2StrArray(str,",\\s*");}/*** 按照yyyy-MM-dd HH:mm:ss的格式,日期转字符串* @param date* @return yyyy-MM-dd HH:mm:ss*/public static String date2Str(Date date){return date2Str(date,"yyyy-MM-dd HH:mm:ss");}/*** 按照yyyy-MM-dd HH:mm:ss的格式,字符串转日期* @param date* @return*/public static Date str2Date(String date){if(notEmpty(date)){SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");try {return sdf.parse(date);} catch (ParseException e) {e.printStackTrace();}return new Date();}else{return null;}}/*** 按照参数format的格式,日期转字符串* @param date* @param format* @return*/public static String date2Str(Date date,String format){if(date!=null){SimpleDateFormat sdf = new SimpleDateFormat(format);return sdf.format(date);}else{return "";}}/*** 把时间根据时、分、秒转换为时间段* @param StrDate*/public static String getTimes(String StrDate){String resultTimes = "";SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date now;try {now = new Date();Date date=df.parse(StrDate);long times = now.getTime()-date.getTime();long day  =  times/(24*60*60*1000);long hour = (times/(60*60*1000)-day*24);long min  = ((times/(60*1000))-day*24*60-hour*60);long sec  = (times/1000-day*24*60*60-hour*60*60-min*60);StringBuffer sb = new StringBuffer();//sb.append("发表于:");if(hour>0 ){sb.append(hour+"小时前");} else if(min>0){sb.append(min+"分钟前");} else{sb.append(sec+"秒前");}resultTimes = sb.toString();} catch (ParseException e) {e.printStackTrace();}return resultTimes;}/*** 验证邮箱* @param email* @return*/public static boolean checkEmail(String email){boolean flag = false;try{String check = "^([a-z0-9A-Z]+[-|_|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$";Pattern regex = Pattern.compile(check);Matcher matcher = regex.matcher(email);flag = matcher.matches();}catch(Exception e){flag = false;}return flag;}/*** 验证手机号码* @return*/public static boolean checkMobileNumber(String mobileNumber){Pattern p = null;Matcher m = null;boolean b = false;p = Pattern.compile("^[1][3,4,5,7,8][0-9]{9}$"); // 验证手机号m = p.matcher(mobileNumber);b = m.matches();return b;}/*** 将驼峰转下划线* @param param* @return*/public static String camelToUnderline(String param){if (param==null||"".equals(param.trim())){return "";}int len=param.length();StringBuilder sb=new StringBuilder(len);for (int i = 0; i < len; i++) {char c=param.charAt(i);if (Character.isUpperCase(c)){sb.append("_");sb.append(Character.toLowerCase(c));}else{sb.append(c);}}return sb.toString();}/*** 去掉下划线并将下划线后的首字母转为大写* @param str* @return*/public static String transformStr(String str){//去掉数据库字段的下划线if(str.contains("_")) {String[] names = str.split("_");String firstPart = names[0];String otherPart = "";for (int i = 1; i < names.length; i++) {String word = names[i].replaceFirst(names[i].substring(0, 1), names[i].substring(0, 1).toUpperCase());otherPart += word;}str = firstPart + otherPart;}return str;}/*** 转换为map* @param list* @return*/public static List<Map<String,Object>> transformMap(List<Map<String,Object>> list){List<Map<String,Object>> resultMapList = new ArrayList<>();for (Map<String, Object> map : list) {Map<String,Object> tempMap = new HashMap<>();for (String s : map.keySet()) {tempMap.put(transformStr(s),map.get(s));}resultMapList.add(tempMap);}return resultMapList;}public static String clearHtml(String content,int p) {if(null==content) return "";if(0==p) return "";Pattern p_script;Matcher m_script;Pattern p_style;Matcher m_style;Pattern p_html;Matcher m_html;try {String regEx_script = "<[\\s]*?script[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?script[\\s]*?>";//定义script的正则表达式{或<script[^>]*?>[\\s\\S]*?<\\/script> }String regEx_style = "<[\\s]*?style[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?style[\\s]*?>";//定义style的正则表达式{或<style[^>]*?>[\\s\\S]*?<\\/style> }String regEx_html = "<[^>]+>"; //定义HTML标签的正则表达式p_script = Pattern.compile(regEx_script,Pattern.CASE_INSENSITIVE);m_script = p_script.matcher(content);content = m_script.replaceAll(""); //过滤script标签p_style = Pattern.compile(regEx_style,Pattern.CASE_INSENSITIVE);m_style = p_style.matcher(content);content = m_style.replaceAll(""); //过滤style标签p_html = Pattern.compile(regEx_html,Pattern.CASE_INSENSITIVE);m_html = p_html.matcher(content);content = m_html.replaceAll(""); //过滤html标签}catch(Exception e) {return "";}if(content.length()>p){content = content.substring(0, p)+"...";}else{content = content + "...";}return content;}public static String md5(String str) {try {MessageDigest md = MessageDigest.getInstance("MD5");md.update(str.getBytes());byte b[] = md.digest();int i;StringBuffer buf = new StringBuffer("");for (int offset = 0; offset < b.length; offset++) {i = b[offset];if (i < 0)i += 256;if (i < 16)buf.append("0");buf.append(Integer.toHexString(i));}str = buf.toString();} catch (Exception e) {e.printStackTrace();}return str;}
}
6.XmlHelper 工具类 直接复制即可
package cn.gdpu.config.pay;import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;/*** @ClassName XmlHelper* @Author ttaurus* @Date Create in 2020/1/21 18:23*/
public class XmlHelper{private final XPath path;private final Document doc;private XmlHelper(InputSource inputSource) throws ParserConfigurationException, SAXException, IOException{DocumentBuilderFactory dbf = getDocumentBuilderFactory();DocumentBuilder db = dbf.newDocumentBuilder();doc = db.parse(inputSource);path = getXPathFactory().newXPath();}private static XmlHelper create(InputSource inputSource) {try {return new XmlHelper(inputSource);} catch (ParserConfigurationException e) {throw new RuntimeException(e);} catch (SAXException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}public static XmlHelper of(InputStream is) {InputSource inputSource = new InputSource(is);return create(inputSource);}public static XmlHelper of(String xmlStr) {StringReader sr = new StringReader(xmlStr.trim());InputSource inputSource = new InputSource(sr);XmlHelper xmlHelper = create(inputSource);IOUtils.closeQuietly(sr);return xmlHelper;}private Object evalXPath(String expression, Object item, QName returnType) {item = null == item ? doc : item;try {return path.evaluate(expression, item, returnType);} catch (XPathExpressionException e) {throw new RuntimeException(e);}}/*** 获取String* @param expression 路径* @return String*/public String getString(String expression) {return (String) evalXPath(expression, null, XPathConstants.STRING);}/*** 获取Boolean* @param expression 路径* @return String*/public Boolean getBoolean(String expression) {return (Boolean) evalXPath(expression, null, XPathConstants.BOOLEAN);}/*** 获取Number* @param expression 路径* @return {Number}*/public Number getNumber(String expression) {return (Number) evalXPath(expression, null, XPathConstants.NUMBER);}/*** 获取某个节点* @param expression 路径* @return {Node}*/public Node getNode(String expression) {return (Node) evalXPath(expression, null, XPathConstants.NODE);}/*** 获取子节点* @param expression 路径* @return NodeList*/public NodeList getNodeList(String expression) {return (NodeList) evalXPath(expression, null, XPathConstants.NODESET);}/*** 获取String* @param node 节点* @param expression 相对于node的路径* @return String*/public String getString(Object node, String expression) {return (String) evalXPath(expression, node, XPathConstants.STRING);}/*** 获取* @param node 节点* @param expression 相对于node的路径* @return String*/public Boolean getBoolean(Object node, String expression) {return (Boolean) evalXPath(expression, node, XPathConstants.BOOLEAN);}/*** 获取* @param node 节点* @param expression 相对于node的路径* @return {Number}*/public Number getNumber(Object node, String expression) {return (Number) evalXPath(expression, node, XPathConstants.NUMBER);}/*** 获取某个节点* @param node 节点* @param expression 路径* @return {Node}*/public Node getNode(Object node, String expression) {return (Node) evalXPath(expression, node, XPathConstants.NODE);}/*** 获取子节点* @param node 节点* @param expression 相对于node的路径* @return NodeList*/public NodeList getNodeList(Object node, String expression) {return (NodeList) evalXPath(expression, node, XPathConstants.NODESET);}/*** 针对没有嵌套节点的简单处理* @return map集合*/public Map<String, String> toMap() {Element root = doc.getDocumentElement();Map<String, String> params = new HashMap<String, String>();// 将节点封装成map形式NodeList list = root.getChildNodes();for (int i = 0; i < list.getLength(); i++) {Node node = list.item(i);if (node instanceof Element) {params.put(node.getNodeName(), node.getTextContent());}}return params;}private static DocumentBuilderFactory getDocumentBuilderFactory(){return XmlHelperHolder.documentBuilderFactory;}private static XPathFactory getXPathFactory() {return  XmlHelperHolder.xPathFactory;}/*** 内部类单例*/private static class XmlHelperHolder {private static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();private static XPathFactory xPathFactory = XPathFactory.newInstance();}}
7.Msg工具类 可直接复制
package cn.gdpu.util;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;public class Msg {public static Object empty(){Map<String, Object> map = new TreeMap<>();map.put("code", 0);map.put("count", 0);map.put("data", new ArrayList());map.put("msg", "搜索结果为空");return JSON.toJSON(map);}public static Object ok(){JSONObject json = new JSONObject();json.put("msg", "ok");return JSON.toJSON(json);}public static Object fail(){JSONObject json = new JSONObject();json.put("msg", "fail");return JSON.toJSON(json);}public static Object msg(String type, String msg){Map<String, String> result = new HashMap<>();result.put(type, msg);return JSON.toJSON(result);}//未登录时返回code -1public static void unlogin(HttpServletResponse response){try {Map<String, Integer> result = new HashMap<>();result.put("code", Integer.valueOf(-1));String jsonStr = JSON.toJSONString(result);response.setContentType("application/json; charset=utf-8");response.getWriter().print(jsonStr);} catch (IOException e) {e.printStackTrace();}}public static Object msg(Object msg){JSONObject json = new JSONObject();json.put("msg", msg);return JSON.toJSON(json);}}
8.MD5Util 可直接复制
package cn.gdpu.util;import cn.gdpu.config.pay.PayConfig;
import com.github.wxpay.sdk.WXPayConstants;import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;/*** @ClassName MD5Util* @Author ttaurus* @Date Create in 2020/1/21 13:56*/
public class MD5Util{public static String getSign(Map<String,String> data) throws Exception{PayConfig config = new PayConfig();Set<String> keySet = data.keySet();String[] keyArray = keySet.toArray(new String[keySet.size()]);Arrays.sort(keyArray);StringBuilder sb = new StringBuilder();for(String k : keyArray){if(k.equals(WXPayConstants.FIELD_SIGN)){continue;}if(data.get(k).trim().length() > 0) // 参数值为空,则不参与签名sb.append(k).append("=").append(data.get(k).trim()).append("&");}sb.append("key=").append(config.getKey());MessageDigest md = null;try{md = MessageDigest.getInstance("MD5");}catch(NoSuchAlgorithmException e){e.printStackTrace();}byte[] array = new byte[0];array = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));StringBuilder sb2 = new StringBuilder();for(byte item : array){sb2.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1 , 3));}System.out.println("签名为:"+sb2);return sb2.toString().toUpperCase();}
}

3.Controller配置

  1. UserWXPayController
package cn.gdpu.userController;import cn.gdpu.bean.GoodSpecificationDetail;
import cn.gdpu.bean.Order;
import cn.gdpu.bean.OrderDetail;
import cn.gdpu.bean.OrderVO;
import cn.gdpu.config.pay.PayConfig;
import cn.gdpu.config.pay.PaymentApi;
import cn.gdpu.config.pay.PaymentKit;
import cn.gdpu.mapper.GoodDao;
import cn.gdpu.mapper.OrderDao;
import cn.gdpu.mapper.OrderDetailDao;
import cn.gdpu.mapper.UserDao;
import cn.gdpu.util.MD5Util;
import cn.gdpu.util.Msg;
import com.alibaba.fastjson.JSON;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @ClassName WXPayController* @Author ttaurus* @Date Create in 2020/1/21 11:24*/
@RestController
@RequestMapping("/user/pay")
public class UserWXPayController {private static Logger logger = LoggerFactory.getLogger(UserWXPayController.class);//可注释@PostMapping(value = "/notify") //回调函数public String payNotify(HttpServletRequest request) {InputStream is = null;String xmlBack = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml> ";try {is = request.getInputStream();// 将InputStream转换成StringBufferedReader reader = new BufferedReader(new InputStreamReader(is));StringBuilder sb = new StringBuilder();String line = null;while ((line = reader.readLine()) != null) {sb.append(line + "\n");}xmlBack = notify(request,sb.toString());  //调用通知方法//System.out.println("返回的信息如下:\n"+sb.toString());//System.out.println("已成功支付且回复到微信服务器!");} catch (Exception e) {logger.error("微信手机支付回调通知失败:", e);} finally {if (is != null) {try {is.close();} catch (IOException e) {e.printStackTrace();}}}//xmlBack = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";//System.out.println("已成功支付且回复到微信服务器!");return xmlBack;}/*** 功能描述: <微信小程序 支付>** @param orderNumber 商户订单号* @param money       订单总金额(分)* @param openid      微信用户的openId* @return* @throws Exception*/@PostMapping("/prepay")public Object genWxProgramPayChannel(HttpServletRequest request,String orderNumber, BigDecimal money, String openid) throws Exception {PayConfig config = new PayConfig();Map<String, String> reqParams = new HashMap<>();//微信分配的小程序IDreqParams.put("appid", config.getAppID());//微信支付分配的商户号reqParams.put("mch_id", config.getMchID());//随机字符串reqParams.put("nonce_str", System.currentTimeMillis() / 1000 + "");//签名类型reqParams.put("sign_type", "MD5");//充值订单 商品描述reqParams.put("body", "TestTest-充值订单-微信小程序");//商户订单号reqParams.put("out_trade_no", orderNumber);//订单总金额,单位为分reqParams.put("total_fee",money.multiply(BigDecimal.valueOf(100)).intValue()+"");//终端IPreqParams.put("spbill_create_ip", "49.233.83.97");//通知地址reqParams.put("notify_url", config.getNOTIFY_URL());//交易类型reqParams.put("trade_type", "JSAPI");//用户标识reqParams.put("openid" , openid);//签名String sign = WXPayUtil.generateSignature(reqParams, config.getKey());//request.getSession().setAttribute(orderNumber,sign);reqParams.put("sign", sign);/*调用支付定义下单API,返回预付单信息 prepay_id*/String xmlResult = PaymentApi.pushOrder(reqParams);logger.info("---"+xmlResult);Map<String, String> result = PaymentKit.xmlToMap(xmlResult);String prepay_id = result.get("prepay_id");/*小程序调起支付数据签名*/Map<String, String> packageParams = new HashMap<String, String>();packageParams.put("appId", config.getAppID());packageParams.put("timeStamp", System.currentTimeMillis() / 1000 + "");packageParams.put("nonceStr", System.currentTimeMillis() + "");packageParams.put("package", "prepay_id=" + prepay_id);packageParams.put("signType", "MD5");String packageSign = WXPayUtil.generateSignature(packageParams, config.getKey());packageParams.put("paySign", packageSign);return packageParams;}public String notify(HttpServletRequest request,String notifyStr) {String xmlBack = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml> ";try {// 转换成mapMap<String, String> resultMap = WXPayUtil.xmlToMap(notifyStr);WXPay wxpayApp = new WXPay(new PayConfig());OrderVO orderVO = new OrderVO();if (wxpayApp.isPayResultNotifySignatureValid(resultMap)) {String returnCode = resultMap.get("return_code");  //状态String transactionId = resultMap.get("transaction_id");System.out.println(transactionId);if (returnCode.equals("SUCCESS")) {//if (StringUtils.isNotBlank(orderNumber)) {/*** 业务代码在此处!* 更新订单状态等等*/xmlBack = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";//}}}} catch (Exception e) {e.printStackTrace();}return xmlBack;}
}
2. 退款Controller 可本地调用
package cn.gdpu.controller;import cn.gdpu.bean.*;
import cn.gdpu.config.pay.PayConfig;
import cn.gdpu.mapper.*;
import cn.gdpu.util.MD5Util;
import cn.gdpu.util.Msg;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.*;/****/
@RestController
@RequestMapping("/admin")
public class AdminController {private static Logger logger = LoggerFactory.getLogger(AdminController.class); //可注释/*** 功能描述: <微信小程序 退款>* 退款操作* @return* @throws Exception*/@PostMapping("refund")public Object refundByAdmin(HttpServletRequest request,@RequestParam("transaction_id")String transaction_id,@RequestParam("total_fee")String total_fee) throws Exception{if(refund(transaction_id,total_fee)){/*** 业务代码 */return Msg.ok(); //消息通知工具类}else{return Msg.msg("出现问题,退款失败!");}}public boolean refund(String transaction_id,String total_fee) throws Exception{Map<String,String> data = new HashMap<String,String>();System.err.println("进入微信退款申请");Date now = new Date();SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");//可以方便地修改日期格式String hehe = dateFormat.format(now);int pay = (int)(Double.parseDouble(total_fee) * 100); //需要*100 String out_refund_no = hehe + "wxrefund";//String transaction_id = "微信支付时生成的流水号";//String total_fee = "微信支付时支付的钱(单位为分)";data.put("out_refund_no" , out_refund_no);data.put("transaction_id" , transaction_id);data.put("total_fee" , String.valueOf(pay));data.put("refund_fee" , String.valueOf(pay));PayConfig config = new PayConfig();WXPay wxpay = new WXPay(config);data.put("appid" , config.getAppID());data.put("mch_id" , config.getMchID());data.put("nonce_str" , WXPayUtil.generateNonceStr());data.put("sign" , MD5Util.getSign(data));Map<String,String> resp = null;try{resp = wxpay.refund(data);}catch(Exception e){e.printStackTrace();}System.err.println("返回信息:\n" + resp);String return_code = resp.get("return_code");   //返回状态码String return_msg = resp.get("return_msg");     //返回信息String resultReturn = null;if("SUCCESS".equals(return_code)){String result_code = resp.get("result_code");       //业务结果String err_code_des = resp.get("err_code_des");     //错误代码描述if("SUCCESS".equals(result_code)){//表示退款申请接受成功,结果通过退款查询接口查询//修改用户订单状态为退款申请中(暂时未写)resultReturn = "退款申请成功";System.out.println(resultReturn);return true;}else{logger.info("订单号:{}错误信息:{}" , err_code_des);resultReturn = err_code_des;return false;}}else{logger.info("订单号:{}错误信息:{}" , return_msg);resultReturn = return_msg;return false;}}
}

最后一步就是微信小程序里调用接口即可。

springboot之微信支付与退款相关推荐

  1. 关于微信支付的退款那些事

    关于微信支付的退款那些事 微信支付的退款 需要双向证书 一个是操作人的电脑上  需要安装的证书 以p12为结尾的 另外一个证书是2个,需要放到服务器上 微信支付的退款,在请求接口的时候,会在发起人的电 ...

  2. php微信支付分取消订单,PHP实现微信支付和退款

    这次给大家带来PHP实现微信支付和退款,PHP实现微信支付和退款的注意事项有哪些,下面就是实战案例,一起来看一下. 之前有写过几篇文章将微信支付和退款: 1.PHP实现微信支付(jsapi支付)流程 ...

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

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

  4. 微信支付-“申请退款”接口遇到curl出错,错误码:58解决方案

    微信支付后 退款,接口遇到curl出错,错误码:58 这个问题基本上是证书没对应上(微信支付不需要申请证书,退款要用到证书),证书要去微信商户号后台(账户中心-API安全-申请证书)下载,已经下载过忘 ...

  5. SpringBoot整合微信支付开发在线教育视频网站(完整版)

    目录 ├─code.zip ├─第 1 章项目介绍和前期准备 │  ├─1-1 SpringBoot整合微信支付开发在线教育视频站点介绍.TS │  ├─1-2 中大型公司里面项目开发流程讲解.TS ...

  6. 用java实现微信支付,退款,部分退款服务端

    由于公司业务需要,最近搞微信退款功能,今天抽空在此记录一下,以后用到也可以到这来看一眼.废话不多说,进入正题. 微信支付以及付款呢,先要有个证书,不清楚的,还要有证书,可以去微信平台看证书怎么下载 h ...

  7. Springboot接入微信支付、支付宝支付

    微信支付 引入sdk <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpa ...

  8. springboot集成微信支付V3 SDK

    微信支付开通支付方法在这里可以参考一下:申请开通微信支付教程_个人怎么申请微信支付_郑鹏川的博客-CSDN博客 因为微信支付回调需要一个外网可访问的地址,我本地调试是采用的内网穿透的方式进行调试的. ...

  9. 微信退款返回58 linux,小程序微信支付申请退款返回cUrl错误,错误码:58

    2019-04-03 15:15:29 如何看待微信公开课小程序热门讨论「小程序微信支付申请退款返回cUrl错误,错误码:58」 摘要:小程序微信支付申请退款返回cUrl错误,错误码:58 展开:调用 ...

最新文章

  1. mysql设置常用——修改大小写、设置sql_mode不支持功能、
  2. Mac是大脑,iPad是四肢 如何实现的呢?右键而已
  3. python生成器和装饰器_python之yield与装饰器
  4. lintcode-137-克隆图
  5. python apply函数_8 个 Python 高效数据分析的技巧
  6. FabFilter Total Bundle 2021 for Mac - 经典效果器合集(2022版)
  7. 4G5G学习过程中整理的专业名词的符号简称
  8. 计算机操作系统核心知识点总结面试笔试要点
  9. 手动引入jar包,解决Dependency ‘XXX‘ not found的两种方式
  10. Google Analytics
  11. 做了个新网站http://qq.ihaonet.com/全球最大QQ聊天交友网站
  12. 2014年电大计算机应用基础考,2014年中央电大计算机应用基础网考最新修改版小抄.doc...
  13. ffmpeg详细安装教程
  14. 编程将输入的百分制成绩转换为五分制成绩输出
  15. old DIB in res XXX ico pass it through SDKPAINT 错误
  16. 瀚博半导体载天VA1 加速卡安装过程
  17. BBEdit 12.6.6 代码编辑器
  18. 部门来了个跳槽出来的测试开发,听说是00后,上来一顿操作给我看呆了...
  19. 中断API之irq_set_chained_handler
  20. idea提交时忽略.class、.iml文件和文件夹或目录的方法

热门文章

  1. 利用 Docker 配置 Pytorch 镜像流程
  2. redis expire命令
  3. java达内项目_达内IT学院举办Java互联网架构师项目峰会
  4. (十四)覆盖率类型、覆盖率组
  5. 软件测试实习生(月薪3k-5k)需要具备哪些技能才能找到工作?
  6. 抽象工厂和工厂方法模式
  7. 华为十年(转贴)原华为牛人写的
  8. 基于知识图谱的智能问答机器人
  9. 正在学习C++的屑人麻了
  10. 计算机专业顶岗实训,计算机专业学生的顶岗实习