注意:(appId账号)微信商户平台必须开通APP支付功能
文件 application.yml新增以下配置
# app微信支付配置 wx: appPay: # appId账号 appId: ****** # 商户号 mchId: ******** # apiV3秘钥 apiV3Key: ********* # 商户证书序列号 mchSerialNo: ********* # 商户私钥路径 privateKeyUrl: ************ # 平台证书路径 wechatPayCertificateUrl: ************ # 支付回调地址 notifyUrl: *************
<!-- 微信 --> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-open</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-mp</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-pay</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-java</artifactId> <version>0.2.7</version> </dependency>
package com.controller.busi;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.busi.config.wxpayv3.IJPayHttpResponse;
import com.busi.config.wxpayv3.PayKit;
import com.busi.config.wxpayv3.RequestMethodEnum;
import com.busi.config.wxpayv3.WxPayApi;
import com.config.WxAppPayConfig;
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.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Api(value = "微信app支付Controller", tags = "app-支付")
@RestController
@RequestMapping("/app/pay")
@Slf4j
public class AppWxPayController {
@Value("${wx.appPay.appId}")
private String appId;
@Value("${wx.appPay.mchId}")
private String mchId;
@Value("${wx.appPay.apiV3Key}")
private String apiV3Key;
@Value("${wx.appPay.mchSerialNo}")
private String mchSerialNo;
@Value("${wx.appPay.privateKeyUrl}")
private String privateKeyUrl;
@Value("${wx.appPay.wechatPayCertificateUrl}")
private String wechatPayCertificateUrl;
@Value("${wx.appPay.notifyUrl}")
private String notifyUrl;
@ApiOperation("创建预支付订单")
@PostMapping("/doUnifiedOrder")
public Map<String, Object> WxPayApp() {
Map<String, Object> payMap = new HashMap<>();
// 调用app微信支付api入参
com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
json.put("appid", appId);
json.put("mchid", mchId);
// 商品描述
json.put("description", "测试商品");
// 订单号
String outTradeNo = generateNonceStr();
json.put("out_trade_no", outTradeNo);
// 回调url
json.put("notify_url", notifyUrl);
com.alibaba.fastjson.JSONObject amountJson = new com.alibaba.fastjson.JSONObject();
amountJson.put("total", 1);
amountJson.put("currency", "CNY");
json.put("amount", amountJson);
// 价格不能小于一分钱
com.alibaba.fastjson.JSONObject jsonObject = WxAppPayConfig.doPostWexinV3("https://api.mch.weixin.qq.com/v3/pay/transactions/app", json.toJSONString(),
privateKeyUrl, wechatPayCertificateUrl, mchId, mchSerialNo);
log.info("调用app微信支付api结果:" + jsonObject);
assert jsonObject != null;
String prepay_id = jsonObject.getString("prepay_id");
// 时间戳
long timestamp = System.currentTimeMillis() / 1000;
// 随机串
String nonceStr = UUID.randomUUID().toString().replace("-", "");
String sign = WxAppPayConfig.getSign(appId, timestamp, nonceStr, prepay_id, privateKeyUrl, wechatPayCertificateUrl);
log.info("签名:" + sign);
payMap.put("prepayid", prepay_id);
payMap.put("timestamp", String.valueOf(timestamp));
payMap.put("noncestr", nonceStr);
payMap.put("sign", sign);
payMap.put("appid", appId);
payMap.put("package", "Sign=WXPay");
payMap.put("extData", "sign");
payMap.put("partnerid", mchId);
// TODO 订单信息入库
// payMap是拉起app微信支付需要的参数
return payMap;
}
@ApiOperation("app微信支付回调")
@GetMapping("/notify")
public void notify(HttpServletRequest request) throws Exception {
log.info("收到app微信支付回调....");
// 获取报文
String body = getRequestBody(request);
// 随机串
String nonce = request.getHeader("Wechatpay-Nonce");
// 微信传递过来的签名
String signature = request.getHeader("Wechatpay-Signature");
// 证书序列号(微信平台)
String wechatPaySerial = request.getHeader("Wechatpay-Serial");
// 时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(Files.newInputStream(Paths.get(privateKeyUrl)));
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
apiV3Key.getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
Verifier verifier = certificatesManager.getVerifier(mchId);
// 构建request,传入必要参数
NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(wechatPaySerial)
.withNonce(nonce)
.withTimestamp(timestamp)
.withSignature(signature)
.withBody(body)
.build();
NotificationHandler handler = new NotificationHandler(verifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
// 验签和解析请求体
Notification notification = handler.parse(notificationRequest);
String result = notification.getDecryptData();
log.info("解密报文:" + result);
com.alibaba.fastjson.JSONObject resultJson = JSON.parseObject(result);
String trade_state = resultJson.getString("trade_state").trim();
String out_trade_no = resultJson.getString("out_trade_no").trim();
String trade_type = resultJson.getString("trade_type").trim();
log.info("微信支付交易状态码:" + trade_state);
log.info("微信支付交易订单号:" + out_trade_no);
log.info("微信支付交易类型:" + trade_type);
// TODO 支付成功,更新订单信息
if ("SUCCESS".equals(trade_state)) {
}
}
/**
* 获取随机字符串 Nonce Str
*/
public String generateNonceStr() {
StringBuilder stringBuffer = new StringBuilder();
int prefix = RandomUtil.randomInt(10000, 99999);
int suffix = RandomUtil.randomInt(10000, 99999);
Long time = System.currentTimeMillis();
return stringBuffer.append(prefix).append(time).append(suffix).toString();
}
private String getRequestBody(HttpServletRequest request) {
StringBuilder sb = new StringBuilder();
try (ServletInputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return sb.toString();
}
// 退款
public static void main(String[] args) throws Exception {
// 查询订单信息
String orderId = "94024170497607658181870";
int amount = 100;
String productName = "测试课程";
com.alibaba.fastjson2.JSONObject jsonObject = new com.alibaba.fastjson2.JSONObject();
jsonObject.put("out_trade_no", orderId);
// 退款单号
String outRefundNo = PayKit.generateStr();
jsonObject.put("out_refund_no", outRefundNo);
jsonObject.put("reason", "取消课程【" + productName + "】");
com.alibaba.fastjson2.JSONObject amountInfo = new com.alibaba.fastjson2.JSONObject();
amountInfo.put("refund", amount);
amountInfo.put("total", amount);
amountInfo.put("currency", "CNY");
jsonObject.put("amount", amountInfo);
// 退款参数
String requestBody = com.alibaba.fastjson2.JSON.toJSON(jsonObject).toString();
System.out.println("退款参数 ===" + requestBody);
IJPayHttpResponse response = WxPayApi.v3(
RequestMethodEnum.POST,
"https://api.mch.weixin.qq.com",
"/v3/refund/domestic/refunds",
"商户号",
"序列号",
null,
"D://file//project//apiclient_key.pem",
requestBody
);
String body = response.getBody();
System.out.println("退款结果:" + body);
}
}
package com.busi.config.wxpayv3;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* @author dori
* @date 2024/4/17 15:58
* @description
*/
public class WxPayApi {
public static IJPayHttpResponse v3(RequestMethodEnum method, String urlPrefix, String urlSuffix, String mchId,
String serialNo, String platSerialNo, String keyPath, String body) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = AuthTypeEnum.RSA.getUrl();
String nonceStr = WxPayKit.generateStr();
return v3(method, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, keyPath, body, nonceStr, timestamp, authType, null);
}
public static IJPayHttpResponse v3(RequestMethodEnum method, String urlPrefix, String urlSuffix,
String mchId, String serialNo, String platSerialNo, String keyPath,
String body, String nonceStr, long timestamp, String authType,
File file) throws Exception {
// 构建 Authorization
String authorization = WxPayKit.buildAuthorization(method, urlSuffix, mchId, serialNo,
keyPath, body, nonceStr, timestamp, authType);
if (StrUtil.isEmpty(platSerialNo)) {
platSerialNo = serialNo;
}
if (method == RequestMethodEnum.GET) {
return get(urlPrefix.concat(urlSuffix), authorization, platSerialNo, null);
} else if (method == RequestMethodEnum.POST) {
return post(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body);
} else if (method == RequestMethodEnum.DELETE) {
return delete(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body);
} else if (method == RequestMethodEnum.UPLOAD) {
return upload(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body, file);
} else if (method == RequestMethodEnum.PUT) {
return put(urlPrefix.concat(urlSuffix), authorization, platSerialNo, body);
}
return null;
}
/**
* @param url 请求url
* @param params 请求参数
* @return {@link String} 请求返回的结果
*/
public static String doGet(String url, Map<String, Object> params) {
return HttpKit.getDelegate().get(url, params);
}
/**
* get 请求
*
* @param url 请求url
* @param params 请求参数
* @param headers 请求头
* @return {@link IJPayHttpResponse} 请求返回的结果
*/
public static IJPayHttpResponse get(String url, Map<String, Object> params, Map<String, String> headers) {
return HttpKit.getDelegate().get(url, params, headers);
}
/**
* get 请求
*
* @param url 请求url
* @param authorization 授权信息
* @param serialNumber 公钥证书序列号
* @param params 请求参数
* @return {@link IJPayHttpResponse} 请求返回的结果
*/
public static IJPayHttpResponse get(String url, String authorization, String serialNumber, Map<String, Object> params) {
return get(url, params, getHeaders(authorization, serialNumber));
}
/**
* post 请求
*
* @param url 请求url
* @param data 请求参数
* @param headers 请求头
* @return {@link IJPayHttpResponse} 请求返回的结果
*/
public static IJPayHttpResponse post(String url, String data, Map<String, String> headers) {
return HttpKit.getDelegate().post(url, data, headers);
}
/**
* post 请求
*
* @param url 请求url
* @param authorization 授权信息
* @param serialNumber 公钥证书序列号
* @param data 请求参数
* @return {@link IJPayHttpResponse} 请求返回的结果
*/
public static IJPayHttpResponse post(String url, String authorization, String serialNumber, String data) {
return post(url, data, getHeaders(authorization, serialNumber));
}
/**
* delete 请求
*
* @param url 请求url
* @param data 请求参数
* @param headers 请求头
* @return {@link IJPayHttpResponse} 请求返回的结果
*/
public static IJPayHttpResponse delete(String url, String data, Map<String, String> headers) {
return HttpKit.getDelegate().delete(url, data, headers);
}
/**
* delete 请求
*
* @param url 请求url
* @param authorization 授权信息
* @param serialNumber 公钥证书序列号
* @param data 请求参数
* @return {@link IJPayHttpResponse} 请求返回的结果
*/
public static IJPayHttpResponse delete(String url, String authorization, String serialNumber, String data) {
return delete(url, data, getHeaders(authorization, serialNumber));
}
/**
* upload 请求
*
* @param url 请求url
* @param params 请求参数
* @param headers 请求头
* @return {@link IJPayHttpResponse} 请求返回的结果
*/
public static IJPayHttpResponse upload(String url, Map<String, Object> params, Map<String, String> headers) {
return HttpKit.getDelegate().post(url, params, headers);
}
/**
* upload 请求
*
* @param url 请求url
* @param authorization 授权信息
* @param serialNumber 公钥证书序列号
* @param data 请求参数
* @param file 上传文件
* @return {@link IJPayHttpResponse} 请求返回的结果
*/
public static IJPayHttpResponse upload(String url, String authorization, String serialNumber, String data, File file) {
Map<String, Object> paramMap = new HashMap<>(2);
paramMap.put("file", file);
paramMap.put("meta", data);
return upload(url, paramMap, getUploadHeaders(authorization, serialNumber));
}
/**
* put 请求
*
* @param url 请求url
* @param data 请求参数
* @param headers 请求头
* @return {@link IJPayHttpResponse} 请求返回的结果
*/
public static IJPayHttpResponse put(String url, String data, Map<String, String> headers) {
return HttpKit.getDelegate().put(url, data, headers);
}
/**
* put 请求
*
* @param url 请求url
* @param authorization 授权信息
* @param serialNumber 公钥证书序列号
* @param data 请求参数
* @return {@link IJPayHttpResponse} 请求返回的结果
*/
public static IJPayHttpResponse put(String url, String authorization, String serialNumber, String data) {
return put(url, data, getHeaders(authorization, serialNumber));
}
public static String doPost(String url, Map<String, String> params) {
return HttpKit.getDelegate().post(url, WxPayKit.toXml(params));
}
public static String doPostSsl(String url, Map<String, String> params, String certPath, String certPass) {
return HttpKit.getDelegate().post(url, WxPayKit.toXml(params), certPath, certPass);
}
public static String doPostSslByProtocol(String url, Map<String, String> params, String certPath, String certPass, String protocol) {
return HttpKit.getDelegate().post(url, WxPayKit.toXml(params), certPath, certPass, protocol);
}
public static String doPostSsl(String url, Map<String, String> params, InputStream certFile, String certPass) {
return HttpKit.getDelegate().post(url, WxPayKit.toXml(params), certFile, certPass);
}
public static String doPostSslByProtocol(String url, Map<String, String> params, InputStream certFile, String certPass, String protocol) {
return HttpKit.getDelegate().post(url, WxPayKit.toXml(params), certFile, certPass, protocol);
}
public static String doPostSsl(String url, Map<String, String> params, String certPath) {
if (params.isEmpty() || !params.containsKey("mch_id")) {
throw new RuntimeException("请求参数中必须包含 mch_id,如接口参考中不包 mch_id, 请使用其他同名构造方法。");
}
String certPass = params.get("mch_id");
return doPostSsl(url, params, certPath, certPass);
}
public static String doPostSslByProtocol(String url, Map<String, String> params, String certPath, String protocol) {
if (params.isEmpty() || !params.containsKey("mch_id")) {
throw new RuntimeException("请求参数中必须包含 mch_id,如接口参考中不包 mch_id, 请使用其他同名构造方法。");
}
String certPass = params.get("mch_id");
return doPostSslByProtocol(url, params, certPath, certPass, protocol);
}
public static String doPostSsl(String url, Map<String, String> params, InputStream certFile) {
if (params.isEmpty() || !params.containsKey("mch_id")) {
throw new RuntimeException("请求参数中必须包含 mch_id,如接口参考中不包 mch_id, 请使用其他同名构造方法。");
}
String certPass = params.get("mch_id");
return doPostSsl(url, params, certFile, certPass);
}
public static String doPostSslByProtocol(String url, Map<String, String> params, InputStream certFile, String protocol) {
if (params.isEmpty() || !params.containsKey("mch_id")) {
throw new RuntimeException("请求参数中必须包含 mch_id,如接口参考中不包 mch_id, 请使用其他同名构造方法。");
}
String certPass = params.get("mch_id");
return doPostSslByProtocol(url, params, certFile, certPass, protocol);
}
public static String doUploadSsl(String url, Map<String, String> params, String certPath, String certPass, String filePath) {
return HttpKit.getDelegate().upload(url, WxPayKit.toXml(params), certPath, certPass, filePath);
}
public static String doUploadSslByProtocol(String url, Map<String, String> params, String certPath, String certPass, String filePath, String protocol) {
return HttpKit.getDelegate().upload(url, WxPayKit.toXml(params), certPath, certPass, filePath, protocol);
}
public static String doUploadSsl(String url, Map<String, String> params, String certPath, String filePath) {
if (params.isEmpty() || !params.containsKey("mch_id")) {
throw new RuntimeException("请求参数中必须包含 mch_id,如接口参考中不包 mch_id, 请使用其他同名构造方法。");
}
String certPass = params.get("mch_id");
return doUploadSsl(url, params, certPath, certPass, filePath);
}
public static String doUploadSslByProtocol(String url, Map<String, String> params, String certPath, String filePath, String protocol) {
if (params.isEmpty() || !params.containsKey("mch_id")) {
throw new RuntimeException("请求参数中必须包含 mch_id,如接口参考中不包 mch_id, 请使用其他同名构造方法。");
}
String certPass = params.get("mch_id");
return doUploadSslByProtocol(url, params, certPath, certPass, filePath, protocol);
}
private static final String OS = System.getProperty("os.name") + "/" + System.getProperty("os.version");
private static final String VERSION = System.getProperty("java.version");
public static Map<String, String> getBaseHeaders(String authorization) {
String userAgent = String.format(
"WeChatPay-IJPay-HttpClient/%s (%s) Java/%s",
WxPayApi.class.getPackage().getImplementationVersion(),
OS,
VERSION == null ? "Unknown" : VERSION);
Map<String, String> headers = new HashMap<>(5);
headers.put("Accept", ContentType.JSON.toString());
headers.put("Authorization", authorization);
headers.put("User-Agent", userAgent);
return headers;
}
public static Map<String, String> getHeaders(String authorization, String serialNumber) {
Map<String, String> headers = getBaseHeaders(authorization);
headers.put("Content-Type", ContentType.JSON.toString());
if (StrUtil.isNotEmpty(serialNumber)) {
headers.put("Wechatpay-Serial", serialNumber);
}
return headers;
}
public static Map<String, String> getUploadHeaders(String authorization, String serialNumber) {
Map<String, String> headers = getBaseHeaders(authorization);
headers.put("Content-Type", "multipart/form-data;boundary=\"boundary\"");
if (StrUtil.isNotEmpty(serialNumber)) {
headers.put("Wechatpay-Serial", serialNumber);
}
return headers;
}
/**
* 构建返回参数
*
* @param response {@link IJPayHttpResponse}
* @return {@link Map}
*/
public static Map<String, Object> buildResMap(IJPayHttpResponse response) {
if (response == null) {
return null;
}
Map<String, Object> map = new HashMap<>(6);
String timestamp = response.getHeader("Wechatpay-Timestamp");
String nonceStr = response.getHeader("Wechatpay-Nonce");
String serialNo = response.getHeader("Wechatpay-Serial");
String signature = response.getHeader("Wechatpay-Signature");
String body = response.getBody();
int status = response.getStatus();
map.put("timestamp", timestamp);
map.put("nonceStr", nonceStr);
map.put("serialNumber", serialNo);
map.put("signature", signature);
map.put("body", body);
map.put("status", status);
return map;
}
}
package com.busi.config.wxpayv3;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.CaseInsensitiveMap;
import cn.hutool.core.util.StrUtil;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
public class IJPayHttpResponse implements Serializable {
private static final long serialVersionUID = 6089103955998013402L;
private String body;
private byte[] bodyByte;
private int status;
private Map<String, List<String>> headers;
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public byte[] getBodyByte() {
return bodyByte;
}
public void setBodyByte(byte[] bodyByte) {
this.bodyByte = bodyByte;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Map<String, List<String>> getHeaders() {
return headers;
}
public void setHeaders(Map<String, List<String>> headers) {
this.headers = headers;
}
public String getHeader(String name) {
List<String> values = this.headerList(name);
return CollectionUtil.isEmpty(values) ? null : values.get(0);
}
private List<String> headerList(String name) {
if (StrUtil.isBlank(name)) {
return null;
} else {
CaseInsensitiveMap<String, List<String>> headersIgnoreCase = new CaseInsensitiveMap<>(getHeaders());
return headersIgnoreCase.get(name.trim());
}
}
@Override
public String toString() {
return "IJPayHttpResponse{" +
"body='" + body + '\'' +
", status=" + status +
", headers=" + headers +
'}';
}
}
package com.busi.config.wxpayv3;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.HmacAlgorithm;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.*;
import java.util.*;
/**
* IJPay 工具类
*
* @author dori
*/
public class PayKit {
/**
* 对象路径前缀
*/
public static final String CLASS_PATH_PREFIX = "classpath:";
/**
* 生成16进制的 sha256 字符串
*
* @param data 数据
* @param key 密钥
* @return sha256 字符串
*/
public static String hmacSha256(String data, String key) {
return SecureUtil.hmac(HmacAlgorithm.HmacSHA256, key).digestHex(data);
}
/**
* SHA1加密文件,生成16进制SHA1字符串<br>
*
* @param dataFile 被加密文件
* @return SHA1 字符串
*/
public static String sha1(File dataFile) {
return SecureUtil.sha1(dataFile);
}
/**
* SHA1加密,生成16进制SHA1字符串<br>
*
* @param data 数据
* @return SHA1 字符串
*/
public static String sha1(InputStream data) {
return SecureUtil.sha1(data);
}
/**
* SHA1加密,生成16进制SHA1字符串<br>
*
* @param data 数据
* @return SHA1 字符串
*/
public static String sha1(String data) {
return SecureUtil.sha1(data);
}
/**
* 生成16进制 MD5 字符串
*
* @param data 数据
* @return MD5 字符串
*/
public static String md5(String data) {
return SecureUtil.md5(data);
}
/**
* AES 解密
*
* @param base64Data 需要解密的数据
* @param key 密钥
* @return 解密后的数据
*/
public static String decryptData(String base64Data, String key) {
return SecureUtil.aes(md5(key).toLowerCase().getBytes()).decryptStr(base64Data);
}
/**
* AES 加密
*
* @param data 需要加密的数据
* @param key 密钥
* @return 加密后的数据
*/
public static String encryptData(String data, String key) {
return SecureUtil.aes(md5(key).toLowerCase().getBytes()).encryptBase64(data.getBytes());
}
/**
* 简化的UUID,去掉了横线,使用性能更好的 ThreadLocalRandom 生成UUID
*
* @return 简化的 UUID,去掉了横线
*/
public static String generateStr() {
return IdUtil.fastSimpleUUID();
}
/**
* 雪花算法
*
* @param workerId 终端ID
* @param dataCenterId 数据中心ID
* @return {@link Snowflake}
*/
public static Snowflake getSnowflake(long workerId, long dataCenterId) {
return IdUtil.getSnowflake(workerId, dataCenterId);
}
/**
* 把所有元素排序
*
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params) {
return createLinkString(params, false);
}
/**
* @param params 需要排序并参与字符拼接的参数组
* @param encode 是否进行URLEncoder
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params, boolean encode) {
return createLinkString(params, "&", encode);
}
/**
* @param params 需要排序并参与字符拼接的参数组
* @param connStr 连接符号
* @param encode 是否进行URLEncoder
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params, String connStr, boolean encode) {
return createLinkString(params, connStr, encode, false);
}
public static String createLinkString(Map<String, String> params, String connStr, boolean encode, boolean quotes) {
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
StringBuilder content = new StringBuilder();
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
// 拼接时,不包括最后一个&字符
if (i == keys.size() - 1) {
if (quotes) {
content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"');
} else {
content.append(key).append("=").append(encode ? urlEncode(value) : value);
}
} else {
if (quotes) {
content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"').append(connStr);
} else {
content.append(key).append("=").append(encode ? urlEncode(value) : value).append(connStr);
}
}
}
return content.toString();
}
/**
* URL 编码
*
* @param src 需要编码的字符串
* @return 编码后的字符串
*/
public static String urlEncode(String src) {
try {
return URLEncoder.encode(src, CharsetUtil.UTF_8).replace("+", "%20");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
/**
* 遍历 Map 并构建 xml 数据
*
* @param params 需要遍历的 Map
* @param prefix xml 前缀
* @param suffix xml 后缀
* @return xml 字符串
*/
public static StringBuffer forEachMap(Map<String, String> params, String prefix, String suffix) {
StringBuffer xml = new StringBuffer();
if (StrUtil.isNotEmpty(prefix)) {
xml.append(prefix);
}
for (Map.Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// 略过空值
if (StrUtil.isEmpty(value)) {
continue;
}
xml.append("<").append(key).append(">");
xml.append(entry.getValue());
xml.append("</").append(key).append(">");
}
if (StrUtil.isNotEmpty(suffix)) {
xml.append(suffix);
}
return xml;
}
/**
* 微信下单 map to xml
*
* @param params Map 参数
* @return xml 字符串
*/
public static String toXml(Map<String, String> params) {
StringBuffer xml = forEachMap(params, "<xml>", "</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();
}
/**
* 构造签名串
*
* @param method {@link RequestMethodEnum} GET,POST,PUT等
* @param url 请求接口 /v3/certificates
* @param timestamp 获取发起请求时的系统当前时间戳
* @param nonceStr 随机字符串
* @param body 请求报文主体
* @return 待签名字符串
*/
public static String buildSignMessage(RequestMethodEnum method, String url, long timestamp, String nonceStr, String body) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add(method.toString());
arrayList.add(url);
arrayList.add(String.valueOf(timestamp));
arrayList.add(nonceStr);
arrayList.add(body);
return buildSignMessage(arrayList);
}
/**
* 构造签名串
*
* @param timestamp 应答时间戳
* @param nonceStr 应答随机串
* @param body 应答报文主体
* @return 应答待签名字符串
*/
public static String buildSignMessage(String timestamp, String nonceStr, String body) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add(timestamp);
arrayList.add(nonceStr);
arrayList.add(body);
return buildSignMessage(arrayList);
}
/**
* 构造签名串
*
* @param signMessage 待签名的参数
* @return 构造后带待签名串
*/
public static String buildSignMessage(ArrayList<String> signMessage) {
if (signMessage == null || signMessage.size() <= 0) {
return null;
}
StringBuilder sbf = new StringBuilder();
for (String str : signMessage) {
sbf.append(str).append("\n");
}
return sbf.toString();
}
/**
* v3 接口创建签名
*
* @param signMessage 待签名的参数
* @param keyPath key.pem 证书路径
* @return 生成 v3 签名
* @throws Exception 异常信息
*/
public static String createSign(ArrayList<String> signMessage, String keyPath) throws Exception {
return createSign(buildSignMessage(signMessage), keyPath);
}
/**
* v3 接口创建签名
*
* @param signMessage 待签名的参数
* @param privateKey 商户私钥
* @return 生成 v3 签名
* @throws Exception 异常信息
*/
public static String createSign(ArrayList<String> signMessage, PrivateKey privateKey) throws Exception {
return createSign(buildSignMessage(signMessage), privateKey);
}
/**
* v3 接口创建签名
*
* @param signMessage 待签名的参数
* @param keyPath key.pem 证书路径
* @return 生成 v3 签名
* @throws Exception 异常信息
*/
public static String createSign(String signMessage, String keyPath) throws Exception {
if (StrUtil.isEmpty(signMessage)) {
return null;
}
// 获取商户私钥
PrivateKey privateKey = PayKit.getPrivateKey(keyPath);
// 生成签名
return RsaKit.encryptByPrivateKey(signMessage, privateKey);
}
/**
* v3 接口创建签名
*
* @param signMessage 待签名的参数
* @param privateKey 商户私钥
* @return 生成 v3 签名
* @throws Exception 异常信息
*/
public static String createSign(String signMessage, PrivateKey privateKey) throws Exception {
if (StrUtil.isEmpty(signMessage)) {
return null;
}
// 生成签名
return RsaKit.encryptByPrivateKey(signMessage, privateKey);
}
/**
* 获取授权认证信息
*
* @param mchId 商户号
* @param serialNo 商户API证书序列号
* @param nonceStr 请求随机串
* @param timestamp 时间戳
* @param signature 签名值
* @param authType 认证类型
* @return 请求头 Authorization
*/
public static String getAuthorization(String mchId, String serialNo, String nonceStr, String timestamp, String signature, String authType) {
Map<String, String> params = new HashMap<>(5);
params.put("mchid", mchId);
params.put("serial_no", serialNo);
params.put("nonce_str", nonceStr);
params.put("timestamp", timestamp);
params.put("signature", signature);
return authType.concat(" ").concat(createLinkString(params, ",", false, true));
}
/**
* 获取商户私钥
*
* @param keyPath 商户私钥证书路径
* @return {@link String} 商户私钥
* @throws Exception 异常信息
*/
public static String getPrivateKeyStr(String keyPath) throws Exception {
return RsaKit.getPrivateKeyStr(getPrivateKey(keyPath));
}
/**
* 获取商户私钥
*
* @param keyPath 商户私钥证书路径
* @return {@link PrivateKey} 商户私钥
* @throws Exception 异常信息
*/
public static PrivateKey getPrivateKey(String keyPath) throws Exception {
String originalKey = getCertFileContent(keyPath);
if (StrUtil.isEmpty(originalKey)) {
throw new RuntimeException("商户私钥证书获取失败");
}
return getPrivateKeyByKeyContent(originalKey);
}
/**
* 获取商户私钥
*
* @param originalKey 私钥文本内容
* @return {@link PrivateKey} 商户私钥
* @throws Exception 异常信息
*/
public static PrivateKey getPrivateKeyByKeyContent(String originalKey) throws Exception {
String privateKey = originalKey
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
return RsaKit.loadPrivateKey(privateKey);
}
/**
* 获取证书
*
* @param inputStream 证书文件
* @return {@link X509Certificate} 获取证书
*/
public static X509Certificate getCertificate(InputStream inputStream) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
cert.checkValidity();
return cert;
} catch (CertificateExpiredException e) {
throw new RuntimeException("证书已过期", e);
} catch (CertificateNotYetValidException e) {
throw new RuntimeException("证书尚未生效", e);
} catch (CertificateException e) {
throw new RuntimeException("无效的证书", e);
}
}
/**
* 获取证书
*
* @param path 证书路径,支持相对路径以及绝得路径
* @return {@link X509Certificate} 获取证书
*/
public static X509Certificate getCertificate(String path) {
if (StrUtil.isEmpty(path)) {
return null;
}
InputStream inputStream;
try {
inputStream = getCertFileInputStream(path);
} catch (IOException e) {
throw new RuntimeException("请检查证书路径是否正确", e);
}
return getCertificate(inputStream);
}
/**
* 获取证书详细信息
*
* @param certificate {@link X509Certificate} 证书
* @return {@link CertificateModel} 获取证书详细信息
*/
public static CertificateModel getCertificateInfo(X509Certificate certificate) {
if (null == certificate) {
return null;
}
CertificateModel model = new CertificateModel();
model.setItself(certificate);
model.setIssuerDn(certificate.getIssuerDN());
model.setSubjectDn(certificate.getSubjectDN());
model.setVersion(certificate.getVersion());
model.setNotBefore(certificate.getNotBefore());
model.setNotAfter(certificate.getNotAfter());
model.setSerialNumber(certificate.getSerialNumber().toString(16));
return model;
}
/**
* 获取证书详细信息
*
* @param path 证书路径,支持相对路径以及绝得路径
* @return {@link CertificateModel} 获取证书详细信息
*/
public static CertificateModel getCertificateInfo(String path) {
X509Certificate certificate = getCertificate(path);
return getCertificateInfo(certificate);
}
/**
* 检查证书是否可用
*
* @param model {@link CertificateModel} 证书详细 model
* @param mchId 商户号
* @param offsetDay 偏移天数,正数向未来偏移,负数向历史偏移
* @return true 有效 false 无效
*/
public static boolean checkCertificateIsValid(CertificateModel model, String mchId, int offsetDay) {
if (null == model) {
return false;
}
Date notAfter = model.getNotAfter();
if (null == notAfter) {
return false;
}
// 证书颁发者
Principal issuerDn = model.getIssuerDn();
if (null == issuerDn || !issuerDn.getName().contains(IJPayConstants.ISSUER)) {
return false;
}
// 证书CN字段
if (StrUtil.isNotEmpty(mchId)) {
Principal subjectDn = model.getSubjectDn();
if (null == subjectDn || !subjectDn.getName().contains(IJPayConstants.CN.concat(mchId.trim()))) {
return false;
}
}
// 证书序列号固定40字节的字符串
String serialNumber = model.getSerialNumber();
if (StrUtil.isEmpty(serialNumber) || serialNumber.length() != IJPayConstants.SERIAL_NUMBER_LENGTH) {
return false;
}
// 偏移后的时间
DateTime dateTime = DateUtil.offsetDay(notAfter, offsetDay);
DateTime now = DateUtil.date();
int compare = DateUtil.compare(dateTime, now);
return compare >= 0;
}
/**
* 检查证书是否可用
*
* @param certificate {@link X509Certificate} 证书
* @param mchId 商户号
* @param offsetDay 偏移天数,正数向未来偏移,负数向历史偏移
* @return true 有效 false 无效
*/
public static boolean checkCertificateIsValid(X509Certificate certificate, String mchId, int offsetDay) {
if (null == certificate) {
return false;
}
CertificateModel model = getCertificateInfo(certificate);
return checkCertificateIsValid(model, mchId, offsetDay);
}
/**
* 检查证书是否可用
*
* @param path 证书路径,支持相对路径以及绝得路径
* @param mchId 商户号
* @param offsetDay 偏移天数,正数向未来偏移,负数向历史偏移
* @return true 有效 false 无效
*/
public static boolean checkCertificateIsValid(String path, String mchId, int offsetDay) {
return checkCertificateIsValid(getCertificateInfo(path), mchId, offsetDay);
}
/**
* 公钥加密
*
* @param data 待加密数据
* @param certificate 平台公钥证书
* @return 加密后的数据
* @throws Exception 异常信息
*/
public static String rsaEncryptOAEP(String data, X509Certificate certificate) throws Exception {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
byte[] dataByte = data.getBytes(StandardCharsets.UTF_8);
byte[] cipherData = cipher.doFinal(dataByte);
return Base64.encode(cipherData);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
} catch (InvalidKeyException e) {
throw new IllegalArgumentException("无效的证书", e);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
}
}
/**
* 私钥解密
*
* @param cipherText 加密字符
* @param privateKey 私钥
* @return 解密后的数据
* @throws Exception 异常信息
*/
public static String rsaDecryptOAEP(String cipherText, PrivateKey privateKey) throws Exception {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] data = Base64.decode(cipherText);
return new String(cipher.doFinal(data), StandardCharsets.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("解密失败");
}
}
/**
* 传入 classPath 静态资源路径返回文件输入流
*
* @param classPath 静态资源路径
* @return InputStream
*/
public static InputStream getFileToStream(String classPath) {
Resource resource = new ClassPathResource(classPath);
return resource.getStream();
}
/**
* 传入 classPath 静态资源路径返回绝对路径
*
* @param classPath 静态资源路径
* @return 绝对路径
*/
public static String getAbsolutePath(String classPath) {
return new ClassPathResource(classPath).getAbsolutePath();
}
/**
* 通过路径获取证书文件的输入流
*
* @param path 文件路径
* @return 文件流
* @throws IOException 异常信息
*/
public static InputStream getCertFileInputStream(String path) throws IOException {
if (StrUtil.isBlank(path)) {
return null;
}
// 绝对地址
File file = new File(path);
if (file.exists()) {
return Files.newInputStream(file.toPath());
}
// 相对地址
return getFileToStream(path);
}
/**
* 通过路径获取证书文件的内容
*
* @param path 文件路径
* @return 文件内容
*/
public static String getCertFileContent(String path) throws IOException {
InputStream certFileInputStream = getCertFileInputStream(path);
return IoUtil.read(certFileInputStream, StandardCharsets.UTF_8);
}
/**
* 获取文件真实路径
*
* @param path 文件地址
* @return 返回文件真实路径
*/
public static String getFilePath(String path) {
if (StrUtil.startWith(path, CLASS_PATH_PREFIX)) {
return getAbsolutePath(path);
} else {
return path;
}
}
}
package com.busi.config.wxpayv3;
/**
* HTTP 请求的方法
*
* @author dori
public enum RequestMethodEnum {
/**
* 上传实质是 post 请求
*/
UPLOAD("POST"),
/**
* post 请求
*/
POST("POST"),
/**
* get 请求
*/
GET("GET"),
/**
* put 请求
*/
PUT("PUT"),
/**
* delete 请求
*/
DELETE("DELETE"),
/**
* options 请求
*/
OPTIONS("OPTIONS"),
/**
* head 请求
*/
HEAD("HEAD"),
/**
* trace 请求
*/
TRACE("TRACE"),
/**
* connect 请求
*/
CONNECT("CONNECT"),
/**
* PATCH 请求
*/
PATCH("PATCH"),
;
private final String method;
RequestMethodEnum(String method) {
this.method = method;
}
@Override
public String toString() {
return this.method;
}
}
package com.config;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
@Component
@Data
public class WxAppPayConfig {
private static CloseableHttpClient httpClient;
public static void setup(String privateKeyUrl, String wechatPayCertificateUrl, String mchId, String mchSerialNo) {
// PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
PrivateKey merchantPrivateKey = null;
X509Certificate wechatPayCertificate = null;
try {
merchantPrivateKey = PemUtil.loadPrivateKey(
new FileInputStream(privateKeyUrl));
wechatPayCertificate = PemUtil.loadCertificate(
new FileInputStream(wechatPayCertificateUrl));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
ArrayList<X509Certificate> listCertificates = new ArrayList<>();
listCertificates.add(wechatPayCertificate);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withWechatPay(listCertificates);
httpClient = builder.build();
}
public static JSONObject doPostWexinV3(String url, String body, String privateKeyUrl, String wechatPayCertificateUrl, String mchId, String mchSerialNo) {
if (httpClient == null) {
setup(privateKeyUrl, wechatPayCertificateUrl, mchId, mchSerialNo);
}
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Content-Type", "application/json;chartset=utf-8");
httpPost.addHeader("Accept", "application/json");
try {
if (body == null) {
throw new IllegalArgumentException("data参数不能为空");
}
StringEntity stringEntity = new StringEntity(body, "utf-8");
httpPost.setEntity(stringEntity);
// 直接执行execute方法,官方会自动处理签名和验签,并进行证书自动更新
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
if (httpResponse.getStatusLine().getStatusCode() == 200) {
String jsonResult = EntityUtils.toString(httpEntity);
return JSONObject.parseObject(jsonResult);
} else {
System.err.println("微信支付错误信息" + EntityUtils.toString(httpEntity));
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 获取签名
public static String getSign(String appId, long timestamp, String nonceStr, String pack, String privateKeyUrl, String wechatPayCertificateUrl) {
String message = buildMessage(appId, timestamp, nonceStr, pack);
String paySign = null;
try {
paySign = sign(message.getBytes(StandardCharsets.UTF_8), privateKeyUrl, wechatPayCertificateUrl);
} catch (Exception e) {
e.printStackTrace();
}
return paySign;
}
private static String buildMessage(String appId, long timestamp, String nonceStr, String pack) {
return appId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ pack + "\n";
}
private static String sign(byte[] message, String privateKeyUrl, String wechatPayCertificateUrl) throws Exception {
PrivateKey merchantPrivateKey = null;
X509Certificate wechatPayCertificate = null;
try {
merchantPrivateKey = PemUtil.loadPrivateKey(
new FileInputStream(privateKeyUrl));
wechatPayCertificate = PemUtil.loadCertificate(
new FileInputStream(wechatPayCertificateUrl));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Signature sign = Signature.getInstance("SHA256withRSA");
// 这里需要一个PrivateKey类型的参数,就是商户的私钥。
sign.initSign(merchantPrivateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
}
文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib
文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang
文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些
文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器
文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距
文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器
文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn
文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios
文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql
文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...
文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120
文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数