Java實(shí)現(xiàn)微信支付的項(xiàng)目實(shí)踐
摘要:最近的一個(gè)項(xiàng)目中涉及到了支付業(yè)務(wù),其中用到了微信支付和支付寶支付,在做的過程中也遇到些問題,所以現(xiàn)在總結(jié)梳理一下,分享給有需要的人,也為自己以后回顧留個(gè)思路。
一、微信支付接入準(zhǔn)備工作:
首先,微信支付,只支持企業(yè)用戶,個(gè)人用戶是不能接入微信支付的,所以要想接入微信支付,首先需要有微信公眾號(hào),這個(gè)的企業(yè)才能申請(qǐng)。有了微信公眾號(hào),就能申請(qǐng)微信支付的相關(guān)內(nèi)容,所以在準(zhǔn)備開始寫代碼之前需要先把下面的這些參數(shù)申請(qǐng)好:公眾賬號(hào)ID、微信支付商戶號(hào)、API密鑰、AppSecret是APPID對(duì)應(yīng)的接口密碼、回調(diào)地址(回調(diào)必須保證外網(wǎng)能訪問到此地址)、發(fā)起請(qǐng)求的電腦IP
二、微信支付流程說明:
有了上面提到的這些參數(shù),那我們就可以接入微信支付了,下面我來看下微信支付的官方文檔(https://pay.weixin.qq.com/wiki/doc/api/index.html)、訪問該地址可以看到有多種支付方式可以選擇,我們這里選擇掃碼支付的方式(https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1)
這里我們選擇模式二,下面看下模式二的時(shí)序圖,如下圖:
模式二與模式一相比,流程更為簡(jiǎn)單,不依賴設(shè)置的回調(diào)支付URL。商戶后臺(tái)系統(tǒng)先調(diào)用微信支付的統(tǒng)一下單接口,微信后臺(tái)系統(tǒng)返回鏈接參數(shù)code_url,商戶后臺(tái)系統(tǒng)將code_url值生成二維碼圖片,用戶使用微信客戶端掃碼后發(fā)起支付。注意:code_url有效期為2小時(shí),過期后掃碼不能再發(fā)起支付。

三、微信支付所需Maven依賴
<!--微信支付SDK-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.3.0</version>
</dependency>
<!-- json處理器:引入gson依賴 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.12</version>
</dependency>
<!-- 二維碼 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
<!-- 生成二維碼 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.3.3</version>
</dependency>四、配置文件添加微信支付所需參數(shù)
# 微信支付相關(guān)參數(shù) wxpay: # 商戶號(hào) mch-id: xxxxxxx # 商戶API證書序列號(hào) mch-serial-no: xxxxxxxxxx # 商戶私鑰文件 # 注意:該文件放在項(xiàng)目根目錄下 private-key-path: ./apiclient_key.pem # APIv3密鑰 api-v3-key: xxxxxxxx # APPID appid: xxxxxxc27e0e7cxxx # 微信服務(wù)器地址 domain: https://api.mch.weixin.qq.com # 接收結(jié)果通知地址 # 注意:每次重新啟動(dòng)ngrok,都需要根據(jù)實(shí)際情況修改這個(gè)配置 notify-domain: https://c7c1-240e-3b5-3015-be0-1bc-9bed-fca4-d09b.ngrok.io
五、微信支付下單代碼實(shí)現(xiàn)
1.Controller層
/**
* native下單
*/
@ApiOperation(value = "native 微信支付下單 返回Image")
@GetMapping("/native")
public BaseRes<String> nativePay(@RequestParam("packageId") Integer packageId) {
return wxPayService.nativePay(packageId);
}
/**
* JSAPI下單
*/
@ApiOperation(value = "JSAPI微信支付下單")
@GetMapping("/jsapi")
public BaseRes<String> jsapiPay(@RequestParam("packageId") Integer packageId,@RequestParam("openId") String openId) {
return wxPayService.jsapiPay(packageId,openId);
}注意:packageId是套餐Id,可根據(jù)情況修改
2.Service層
BaseRes<String> nativePay(Integer packageId); BaseRes<String> jsapiPay(Integer packageId, String openId);
3.實(shí)現(xiàn)層
/**
* Mavicat下單
* @return
* @throws Exception
*/
@Transactional(rollbackFor = Exception.class)
@Override
@SneakyThrows
public BaseRes<String> nativePay(Integer packageId){
log.info("發(fā)起Navicat支付請(qǐng)求");
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
CloseableHttpResponse response = wxPayExecute(packageId, null, httpPost);
try {
String bodyAsString = EntityUtils.toString(response.getEntity());//響應(yīng)體
int statusCode = response.getStatusLine().getStatusCode();//響應(yīng)狀態(tài)碼
if (statusCode == 200) { //處理成功
log.info("成功, 返回結(jié)果 = " + bodyAsString);
} else if (statusCode == 204) { //處理成功,無返回Body
log.info("成功");
} else {
log.info("Native下單失敗,響應(yīng)碼 = " + statusCode + ",返回結(jié)果 = " + bodyAsString);
throw new IOException("request failed");
}
Gson gson = new Gson();
//響應(yīng)結(jié)果
Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
//二維碼
String codeUrl = resultMap.get("code_url");
return new BaseRes<>(codeUrl,ServiceCode.SUCCESS);
//生成二維碼
// WxPayUtil.makeQRCode(codeUrl);
} finally {
response.close();
}
}
/**
* JSAPI下單
* @return
*/
@Override
@SneakyThrows
public BaseRes<String> jsapiPay(Integer packageId, String openId) {
log.info("發(fā)起Navicat支付請(qǐng)求");
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.JSAPI_PAY.getType()));
CloseableHttpResponse response = wxPayExecute(packageId, openId, httpPost);
try {
String bodyAsString = EntityUtils.toString(response.getEntity());//響應(yīng)體
int statusCode = response.getStatusLine().getStatusCode();//響應(yīng)狀態(tài)碼
if (statusCode == 200) { //處理成功
log.info("成功, 返回結(jié)果 = " + bodyAsString);
} else if (statusCode == 204) { //處理成功,無返回Body
log.info("成功");
} else {
log.info("JSAPI下單失敗,響應(yīng)碼 = " + statusCode + ",返回結(jié)果 = " + bodyAsString);
throw new IOException("request failed");
}
Gson gson = new Gson();
//響應(yīng)結(jié)果
Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
String prepayId = resultMap.get("prepay_id");
return new BaseRes<>(prepayId,ServiceCode.SUCCESS);
} finally {
response.close();
}
}
// 封裝統(tǒng)一下單方法
private CloseableHttpResponse wxPayExecute(Integer packageId,String openId,HttpPost httpPost) throws IOException {
// 獲取套餐金額 還有相關(guān)信息
ChatPackage chatPackage = chatPackageMapper.selectById(packageId);
if (null == chatPackage) {
throw new NingException(ServiceCode.FAILED);
}
BigDecimal amount = chatPackage.getAmount();
if (null == amount || amount.equals(BigDecimal.ZERO)) {
throw new NingException(ServiceCode.SUCCESS);
}
// 從登錄信息中獲取用戶信息
TokenUser loginUserInfo = CommUtils.getLoginUserInfo();
Integer userId = loginUserInfo.getUserId();
// 請(qǐng)求body參數(shù)
Gson gson = new Gson();
Map<String,Object> paramsMap = new HashMap<>();
paramsMap.put("appid", wxPayConfig.getAppid());
paramsMap.put("mchid", wxPayConfig.getMchId());
paramsMap.put("description", chatPackage.getName());
paramsMap.put("out_trade_no", WxPayUtil.generateOrderNumber(userId,packageId)); //訂單號(hào)
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxApiType.NATIVE_NOTIFY.getType()));
Map<String,Object> amountMap = new HashMap<>();
//由單位:元 轉(zhuǎn)換為單位:分,并由Bigdecimal轉(zhuǎn)換為整型
BigDecimal total = amount.multiply(new BigDecimal(100));
amountMap.put("total", total.intValue());
amountMap.put("currency", "CNY");
paramsMap.put("amount", amountMap);
// 判斷是Navicat下單還是JSAPI下單 JSAPI需要傳OPENID
if (StringUtils.isNotBlank(openId)) {
Map<String,Object> payerMap = new HashMap<>();
payerMap.put("openid",openId);
paramsMap.put("payer",payerMap);
}
JSONObject attachJson = new JSONObject();
attachJson.put("packageId",packageId);
attachJson.put("userId",userId);
attachJson.put("total",total);
paramsMap.put("attach",attachJson.toJSONString());
//將參數(shù)轉(zhuǎn)換成json字符串
String jsonParams = gson.toJson(paramsMap);
log.info("請(qǐng)求參數(shù) ===> {}" , jsonParams);
StringEntity entity = new StringEntity(jsonParams, "utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成簽名并執(zhí)行請(qǐng)求
return wxPayClient.execute(httpPost);
}六、微信支付回調(diào)接口
1.Controller層
/**
* 支付通知
* 微信支付通過支付通知接口將用戶支付成功消息通知給商戶
*/
@ApiOperation(value = "支付通知", notes = "支付通知")
@PostMapping("/pay/notify")
@ClientAuthControl
public WxRes nativeNotify() {
return wxPayService.nativeNotify();
}2.Service層
WxRes nativeNotify();
3.實(shí)現(xiàn)層
@Resource
private Verifier verifier;
private final ReentrantLock lock = new ReentrantLock();
@Override
@SneakyThrows
@Transactional
public WxRes nativeNotify() {
HttpServletRequest request = CommUtils.getRequest();
HttpServletResponse response = CommUtils.getResponse();
Gson gson = new Gson();
try {
//處理通知參數(shù)
String body = WxPayUtil.readData(request);
Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
String requestId = (String) bodyMap.get("id");
//簽名的驗(yàn)證
WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
= new WechatPay2ValidatorForRequest(verifier, requestId, body);
if (wechatPay2ValidatorForRequest.validate(request)) {
throw new RuntimeException();
}
log.info("通知驗(yàn)簽成功");
//處理訂單
processOrder(bodyMap);
return new WxRes("SUCCESS","成功");
} catch (Exception e) {
e.printStackTrace();
response.setStatus(500);
return new WxRes("FAIL","成功");
}
}
/**
* 處理訂單
*
* @param bodyMap
*/
@Transactional
@SneakyThrows
public void processOrder(Map<String, Object> bodyMap){
log.info("處理訂單");
//解密報(bào)文
String plainText = decryptFromResource(bodyMap);
//將明文轉(zhuǎn)換成map
Gson gson = new Gson();
HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
String orderNo = (String) plainTextMap.get("out_trade_no");
String attach = (String) plainTextMap.get("attach");
JSONObject attachJson = JSONObject.parseObject(attach);
Integer packageId = attachJson.getInteger("packageId");
Integer userId = attachJson.getInteger("userId");
Integer total = attachJson.getInteger("total");
/*在對(duì)業(yè)務(wù)數(shù)據(jù)進(jìn)行狀態(tài)檢查和處理之前,
要采用數(shù)據(jù)鎖進(jìn)行并發(fā)控制,
以避免函數(shù)重入造成的數(shù)據(jù)混亂*/
//嘗試獲取鎖:
// 成功獲取則立即返回true,獲取失敗則立即返回false。不必一直等待鎖的釋放
if (lock.tryLock()) {
try {
log.info("plainText={}",plainText);
//處理重復(fù)的通知
//接口調(diào)用的冪等性:無論接口被調(diào)用多少次,產(chǎn)生的結(jié)果是一致的。
String orderStatus = orderService.getOrderStatus(orderNo);
if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
return;
}
// TODO 修改訂單狀態(tài)、添加支付記錄等
// 通知前端用戶 已完成支付
messageSocketHandle.sendMessageByUserID(userId,new TextMessage("PaySuccess"));
} finally {
//要主動(dòng)釋放鎖
lock.unlock();
}
}
}
/**
* 對(duì)稱解密
*
* @param bodyMap
* @return
*/
@SneakyThrows
private String decryptFromResource(Map<String, Object> bodyMap) {
log.info("密文解密");
//通知數(shù)據(jù)
Map<String, String> resourceMap = (Map) bodyMap.get("resource");
//數(shù)據(jù)密文
String ciphertext = resourceMap.get("ciphertext");
//隨機(jī)串
String nonce = resourceMap.get("nonce");
//附加數(shù)據(jù)
String associatedData = resourceMap.get("associated_data");
AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
//數(shù)據(jù)明文
String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
return plainText;
}七、工具類和相關(guān)配置類
1.WxPayUtil工具類
@Slf4j
public class WxPayUtil {
private static final Random random = new Random();
// 生成訂單號(hào)
public static String generateOrderNumber(int userId, int packageId) {
// 獲取當(dāng)前時(shí)間戳
long timestamp = System.currentTimeMillis();
// 生成6位隨機(jī)數(shù)
int randomNum = random.nextInt(900000) + 100000;
// 組裝訂單號(hào)
return String.format("%d%d%d%d", timestamp, randomNum, userId, packageId);
}
/**
* 生成二維碼
* @param url
*/
public static void makeQRCode(String url){
HttpServletResponse response = CommUtils.getResponse();
//通過支付鏈接生成二維碼
HashMap<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
hints.put(EncodeHintType.MARGIN, 2);
try {
BitMatrix bitMatrix = new MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, 200, 200, hints);
MatrixToImageWriter.writeToStream(bitMatrix, "PNG", response.getOutputStream());
System.out.println("創(chuàng)建二維碼完成");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 將通知參數(shù)轉(zhuǎn)化為字符串
*
* @param request
* @return
*/
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null; ) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}2.微信支付配置類
@Configuration
@ConfigurationProperties(prefix = "wxpay") //讀取wxpay節(jié)點(diǎn)
@Data //使用set方法將wxpay節(jié)點(diǎn)中的值填充到當(dāng)前類的屬性中
@Slf4j
public class WxPayConfig {
// 商戶號(hào)
private String mchId;
// 商戶API證書序列號(hào)
private String mchSerialNo;
// 商戶私鑰文件
private String privateKeyPath;
// APIv3密鑰
private String apiV3Key;
// APPID
private String appid;
// 微信服務(wù)器地址
private String domain;
// 接收結(jié)果通知地址
private String notifyDomain;
/**
* 獲取商戶的私鑰文件
*
* @param filename
* @return
*/
private PrivateKey getPrivateKey(String filename) {
try {
return PemUtil.loadPrivateKey(new FileInputStream(filename));
} catch (FileNotFoundException e) {
throw new RuntimeException("私鑰文件不存在", e);
}
}
/**
* 獲取簽名驗(yàn)證器
*
* @return
*/
@Bean
public ScheduledUpdateCertificatesVerifier getVerifier() {
log.info("獲取簽名驗(yàn)證器");
//獲取商戶私鑰
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//私鑰簽名對(duì)象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
//身份認(rèn)證對(duì)象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 使用定時(shí)更新的簽名驗(yàn)證器,不需要傳入證書
ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
wechatPay2Credentials,
apiV3Key.getBytes(StandardCharsets.UTF_8));
return verifier;
}
/**
* 獲取http請(qǐng)求對(duì)象
*
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) {
log.info("獲取httpClient");
//獲取商戶私鑰
PrivateKey privateKey = getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// ... 接下來,你仍然可以通過builder設(shè)置各種參數(shù),來配置你的HttpClient
// 通過WechatPayHttpClientBuilder構(gòu)造的HttpClient,會(huì)自動(dòng)的處理簽名和驗(yàn)簽,并進(jìn)行證書自動(dòng)更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* 獲取HttpClient,無需進(jìn)行應(yīng)答簽名驗(yàn)證,跳過驗(yàn)簽的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient() {
//獲取商戶私鑰
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//用于構(gòu)造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//設(shè)置商戶信息
.withMerchant(mchId, mchSerialNo, privateKey)
//無需進(jìn)行簽名驗(yàn)證、通過withValidator((response) -> true)實(shí)現(xiàn)
.withValidator((response) -> true);
// 通過WechatPayHttpClientBuilder構(gòu)造的HttpClient,會(huì)自動(dòng)的處理簽名和驗(yàn)簽,并進(jìn)行證書自動(dòng)更新
CloseableHttpClient httpClient = builder.build();
log.info("== getWxPayNoSignClient END ==");
return httpClient;
}
}3.微信支付枚舉類
@AllArgsConstructor
@Getter
public enum WxApiType {
/**
* Native下單
*/
NATIVE_PAY("/v3/pay/transactions/native"),
/**
* JSAPI下單
*/
JSAPI_PAY("/v3/pay/transactions/jsapi"),
/**
* 查詢訂單
*/
ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),
/**
* 關(guān)閉訂單
*/
CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),
/**
* 支付通知
*/
NATIVE_NOTIFY("/client/order/pay/notify");
/**
* 類型
*/
private final String type;
}4.簽名驗(yàn)證類
@Slf4j
public class WechatPay2ValidatorForRequest {
/**
* 應(yīng)答超時(shí)時(shí)間,單位為分鐘
*/
protected static final long RESPONSE_EXPIRED_MINUTES = 5;
protected final Verifier verifier;
protected final String requestId;
protected final String body;
public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body) {
this.verifier = verifier;
this.requestId = requestId;
this.body = body;
}
protected static IllegalArgumentException parameterError(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("parameter error: " + message);
}
protected static IllegalArgumentException verifyFail(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("signature verify fail: " + message);
}
public final boolean validate(HttpServletRequest request) throws IOException {
try {
//處理請(qǐng)求參數(shù)
validateParameters(request);
//構(gòu)造驗(yàn)簽名串
String message = buildMessage(request);
String serial = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
//驗(yàn)簽
if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
serial, message, signature, requestId);
}
} catch (IllegalArgumentException e) {
log.error(e.getMessage());
return false;
}
return true;
}
protected final void validateParameters(HttpServletRequest request) {
// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
String header = null;
for (String headerName : headers) {
header = request.getHeader(headerName);
if (header == null) {
throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
}
}
//判斷請(qǐng)求是否過期
String timestampStr = header;
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
// 拒絕過期請(qǐng)求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
}
} catch (DateTimeException | NumberFormatException e) {
throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
}
}
protected final String buildMessage(HttpServletRequest request) throws IOException {
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
protected final String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
}
}到此這篇關(guān)于Java 實(shí)現(xiàn)微信支付的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)Java 微信支付內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java如何跳出當(dāng)前的多重嵌套循環(huán)的問題
Java中的循環(huán)結(jié)構(gòu)包括for循環(huán)、while循環(huán)、do-while循環(huán)和增強(qiáng)型for循環(huán),每種循環(huán)都有其適用場(chǎng)景,在循環(huán)中,break、continue和return分別用于跳出循環(huán)、跳過當(dāng)前循環(huán)和結(jié)束當(dāng)前方法,對(duì)于多重嵌套循環(huán)2025-01-01
SpringBoot結(jié)合Mybatis實(shí)現(xiàn)創(chuàng)建數(shù)據(jù)庫(kù)表的方法
本文主要介紹了SpringBoot結(jié)合Mybatis實(shí)現(xiàn)創(chuàng)建數(shù)據(jù)庫(kù)表的方法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
利用Java反射機(jī)制實(shí)現(xiàn)對(duì)象相同字段的復(fù)制操作
這篇文章主要介紹了利用Java反射機(jī)制實(shí)現(xiàn)對(duì)象相同字段的復(fù)制操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-08-08
Springboot如何實(shí)現(xiàn)自定義異常數(shù)據(jù)
這篇文章主要介紹了Springboot如何實(shí)現(xiàn)自定義異常數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
SpringCloud-Hystrix實(shí)現(xiàn)原理總結(jié)
通過hystrix可以解決雪崩效應(yīng)問題,它提供了資源隔離、降級(jí)機(jī)制、融斷、緩存等功能。接下來通過本文給大家分享SpringCloud-Hystrix實(shí)現(xiàn)原理,感興趣的朋友一起看看吧2021-05-05
SpringBoot接口正確接收時(shí)間參數(shù)的幾種方式
這篇文章主要給大家介紹了關(guān)于SpringBoot接口正確接收時(shí)間參數(shù)的相關(guān)資料,文中通過代碼示例介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用springboot具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09
詳解使用Spring Security OAuth 實(shí)現(xiàn)OAuth 2.0 授權(quán)
本篇文章主要介紹了詳解使用Spring Security OAuth 實(shí)現(xiàn)OAuth 2.0 授權(quán),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-01-01
Java中instanceof關(guān)鍵字實(shí)例講解
大家好,本篇文章主要講的是Java中instanceof關(guān)鍵字實(shí)例講解,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01

