您好,登錄后才能下訂單哦!
這篇文章主要講解了“Java怎么提供給第三方使用接口”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Java怎么提供給第三方使用接口”吧!
在寫接口前一定要簽名做簽名校驗(yàn),我的簽名方式做了特殊處理,因?yàn)榻涌谑菍ν忾_放的,這個(gè)是為了避免惡意調(diào)用接口做的處理,叫做簽名的混淆值,這個(gè)簽名混淆值的作用是就算別人知道了接口,并且知道簽名方式也不能被攻擊,是為了避免被惡意篡改數(shù)據(jù),簽名混淆值就是一組特定加密后的數(shù)據(jù)。
@PostMapping("refundDeductionPoints") public Result<SysIntegralStatement> refundDeductionPoints (@RequestParam Map<String,String> params){ Result<SysIntegralStatement> result = new Result<SysIntegralStatement>(); try { //簽名校驗(yàn) String msgDigest = params.get("msgDigest");//簽名 String msgData = params.get("msgData"); String timeStamp = params.get("timeStamp"); String secret = params.get("secret");// 秘鑰 String sign = SignUtil.sign(msgData+"wf8la1tw7p9o2xz",timeStamp);//wf8la1tw7p9o2xz為簽名混淆值 if (!msgDigest.equals(sign)) { return result.setCode(1006).setReason("數(shù)字簽名無效"); } if (Common.isEmpty(secret)) {//先簽名后冪等校驗(yàn) return result.setCode(1001).setReason("密鑰不能為空"); } /** * 冪等校驗(yàn) * 1.同一個(gè)用戶操作同一個(gè)退貨單一分鐘內(nèi)操作該單據(jù)視為重復(fù)操作(此秘鑰已通過特殊處理) */ String value = redistempalte.opsForValue().get(secret); if (Common.isNotEmpty(value)) { logger.error("重復(fù)請求 secret={}",value); return result.setCode(1007).setReason("重復(fù)請求"); } redistempalte.opsForValue().set(secret, "1",60,TimeUnit.SECONDS);//設(shè)置緩存一分鐘 return service.refundDeductionPoints(params); } catch (Exception e) { logger.error("添加積分流水異常", e); return result.setCode(ErrorCodes.BUSINESS_ERROR).setReason("生成積分流水失敗"); } }
此接口做冪等性校驗(yàn),冪等性校驗(yàn)常見解決方案有很多,可以自行根據(jù)實(shí)際情況選擇,
說到冪等首先要先了解什么是冪等
概念:
冪等(idempotent、idempotence)是一個(gè)數(shù)學(xué)與計(jì)算機(jī)學(xué)概念,常見于抽象代數(shù)中。
在編程中.一個(gè)冪等操作的特點(diǎn)是其任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同。冪等函數(shù),或冪等方法,是指可以使用相同參數(shù)重復(fù)執(zhí)行,并能獲得相同結(jié)果的函數(shù)。
這些函數(shù)不會影響系統(tǒng)狀態(tài),也不用擔(dān)心重復(fù)執(zhí)行會對系統(tǒng)造成改變。例如,“getUsername()和setTrue()”函數(shù)就是一個(gè)冪等函數(shù).
冪等性, 通俗的說就是一個(gè)接口, 多次發(fā)起同一個(gè)請求, 必須保證操作只能執(zhí)行一次,比如:
訂單接口, 不能多次創(chuàng)建訂單
支付接口, 重復(fù)支付同一筆訂單只能扣一次錢
支付寶回調(diào)接口, 可能會多次回調(diào), 必須處理重復(fù)回調(diào)
普通表單提交接口, 因?yàn)榫W(wǎng)絡(luò)超時(shí)等原因多次點(diǎn)擊提交, 只能成功一次
等等
解決方案常見的幾種方式
唯一索引 – 防止新增臟數(shù)據(jù)
token機(jī)制 – 防止頁面重復(fù)提交
悲觀鎖 – 獲取數(shù)據(jù)的時(shí)候加鎖(鎖表或鎖行)
樂觀鎖 – 基于版本號version實(shí)現(xiàn), 在更新數(shù)據(jù)那一刻校驗(yàn)數(shù)據(jù)
分布式鎖 – redis(jedis、redisson)或zookeeper實(shí)現(xiàn)
狀態(tài)機(jī) – 狀態(tài)變更, 更新數(shù)據(jù)時(shí)判斷狀態(tài)
如果有小伙伴不理解什么是冪等可以看看官方是解釋
@Transactional @Override public Result<SysIntegralStatement> refundDeductionPoints(Map<String, String> params) { String msgData = params.get("msgData"); ParamIntegral entity = new Gson().fromJson(msgData, ParamIntegral.class); if (Common.isNull(entity)) { return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("請求參數(shù)不能為空"); } if (Common.isEmpty(entity.getBitems())) { return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("請求參數(shù)不能為空"); } int row = 0; for (ParamIntegral bitem : entity.getBitems()) { if (Common.isEmpty(bitem.getDdh())) { return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("訂單號為必傳參數(shù)"); } if (null == bitem.getJfz()) { return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("扣減積分不能為空"); } List<MallOrderInfo> orderInfo = mallOrderInfoMapper.selectByDdh(bitem.getDdh()); if (orderInfo == null) { return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR) .setReason("訂單號為" + bitem.getDdh() + "沒有此訂單請聯(lián)系客服核對信息。"); } if (orderInfo != null && orderInfo.size() > 1) { return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR) .setReason("訂單號為" + bitem.getDdh() + "有多個(gè)相同訂單請聯(lián)系客服核對信息。"); } if (!"E".equals(orderInfo.get(0).getDdzt())) { return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR) .setReason("訂單號為" + bitem.getDdh() + "未確認(rèn)收貨還沒產(chǎn)生積分不允許退貨。"); } SysIntegral integral = Common.first(integralMapper.selectByMdbm(orderInfo.get(0).getMdbm())); if (integral == null) { return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR) .setReason("門店編碼為" + orderInfo.get(0).getMdbm() + "積分匯總沒有找到此門店,請聯(lián)系客服核實(shí)"); } BigDecimal kyjf = BigDecimal.ZERO; if (entity.getReturnGoods() == true) { // 可用積分小于扣減積分不夠扣ERP使用前抵扣 if (bitem.getJfz().compareTo(integral.getKyjf()) == 1) { kyjf = BigDecimal.ZERO; } else { // 可用積分 = 當(dāng)前可用積分-扣減積分 kyjf = Common.nvl(integral.getKyjf(), BigDecimal.ZERO).subtract(bitem.getJfz()); } } else { // 可用積分 = 當(dāng)前可用積分+退還積分 kyjf = Common.nvl(integral.getKyjf(), BigDecimal.ZERO).add(bitem.getJfz()); } // 更新積分匯總 SysIntegral dataMap = new SysIntegral(); dataMap.setIntegralId(integral.getIntegralId()); dataMap.setMdbm(integral.getMdbm()); dataMap.setKyjf(kyjf); dataMap.setUpdateTime(new Date()); dataMap.setUpdateUser(entity.getUserName()); dataMap.setUpdateUserid(entity.getUserId().intValue()); row = integralMapper.updateByPrimaryKeySelective(dataMap); if (row == 0) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("更新積分失敗"); } //推送到ERP門店信息 BdMdxxH mdxx =new BdMdxxH(); mdxx.setMdbm(integral.getMdbm()); mdxx.setMdjf(kyjf); com.lkfs.cw.common.Result<BdMdxxH> bdMdxxh = dataBaseServiceApi.updateStorePoints(mdxx); if (!bdMdxxh.isComplete()) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); return new Result<SysIntegralStatement>().setCode(bdMdxxh.getCode()).setReason(bdMdxxh.getReason()); } SysIntegralStatement statement = new SysIntegralStatement(); if (entity.getReturnGoods() == true) { statement.setJfz(bitem.getJfz().negate());// 消費(fèi)的積分值 if (bitem.getJfz().compareTo(integral.getKyjf()) == 1) {// 可用積分小于扣減積分不夠扣ERP使用前抵扣 statement.setTzhjfz(BigDecimal.ZERO);// 調(diào)整后積分值 } else { statement.setTzhjfz(Common.nvl(integral.getKyjf(), BigDecimal.ZERO).subtract(bitem.getJfz()));// 調(diào)整后積分值 } statement.setJfxflx("E");// 積分支出 statement.setXxsm("退貨扣減積分(訂單號為:" + bitem.getDdh() + "," + "退貨單號為:" + entity.getDjh() + ")" + "已扣除:" + bitem.getJfz().negate() + ":積分"); } else {// 取消退貨 statement.setJfxflx("I");// 積分收入 statement.setJfz(bitem.getJfz());// 取消退貨把積分贈送回來 statement.setTzhjfz(Common.nvl(integral.getKyjf(), BigDecimal.ZERO).add(bitem.getJfz()));// 調(diào)整后積分值 statement.setXxsm("取消退貨(訂單號為:" + bitem.getDdh() + "," + "退貨單號為:" + entity.getDjh() + ")" + "已退還:" + bitem.getJfz() + ":積分"); } statement.setIntegralId(integral.getIntegralId());// 該門店積分編碼 statement.setTzqjfz(integral.getKyjf());// 調(diào)整前積分值 statement.setDdh(entity.getDdh()); statement.setCreateTime(new Date());// 流水生成時(shí)間 statement.setCreateUser(entity.getUserName()); statement.setCreateUserid(entity.getUserId().intValue()); statement.setJftz("T");// 積分扣減為T statement.setZt("Y");// 狀態(tài) Y:有效 row = mapper.insert(statement); if (row == 0) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("插入積分流水失敗"); } } return new Result<SysIntegralStatement>().setCode(ErrorCodes.SUCCESS).setReason("操作成功"); }
模擬第三方合作方調(diào)用接口
//此方式以上已寫了封裝信息就不一一展示了,可以根據(jù)實(shí)際情況自行操作 private void pushIntegral(Long djlsh) { FiSjysjsH fiSjysjsh = mapper.selectByPrimaryKey(djlsh); //訂單退貨調(diào)用某某退貨扣減積分接口 List<FiSjysjsB> sjysjsbList = bmapper.selectByKhddh(djlsh); if (sjysjsbList != null && sjysjsbList.size() > 0) { List<ParamIntegral> list = new ArrayList<ParamIntegral>(); for (FiSjysjsB bitem : sjysjsbList) { ParamIntegral temp = new ParamIntegral(); temp.setDdh(bitem.getKhddh()); temp.setJfz(bitem.getJfz()); list.add(temp); } ParamIntegral param = new ParamIntegral(); param.setBitems(list); param.setDjh(fiSjysjsh.getDjh()); param.setUserId(AppRealm.getCurrentUser().getUserId()); param.setUserName(AppRealm.getCurrentUser().getUserName()); if (new Short("1").equals(fiSjysjsh.getLocked())) { param.setReturnGoods(true); }else { param.setReturnGoods(false); } String msgData = new Gson().toJson(param).toString(); Map<String, String> params = new HashMap<String, String>(); String timeStamp = String.valueOf(System.currentTimeMillis());//時(shí)間戳 params.put("timeStamp", timeStamp); params.put("msgData", msgData); params.put("msgDigest", SignUtil.sign(msgData+"wf8la1tw7p9o2xz", timeStamp));//生成簽名第二個(gè)值暫定(wf8la1tw7p9o2xz簽名混淆值) params.put("secret",IDEMPOTENT_SECRET_PREFIX + fiSjysjsh.getDjh() + AppRealm.getCurrentUser().getUserId()+param.getReturnGoods() );//自定義密鑰 做冪等校驗(yàn) String result = HttpCilent.post(B2B_URL, params); //發(fā)送http post請求 B2bIntegralResponse res = new Gson().fromJson(result, B2bIntegralResponse.class); if (null == res) { throw new RuntimeException("調(diào)用積分接口系統(tǒng)異常"); } if (res.getCode() != 0) {//接口返回失敗異常代碼提示 throw new RuntimeException("調(diào)用積分接口發(fā)生異常,異常代碼為:"+res.getCode()+"異常信息為:"+res.getReason()); } } }
package com.cy.xgsm.util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; /** * * @author Dylan * */ public class SignUtil { private static final Logger log = LoggerFactory.getLogger(SignUtil.class); /** * */ public static String sign(String str, String secret) { StringBuilder enValue = new StringBuilder(); enValue.append(secret); enValue.append(str); enValue.append(secret); return encryptByMD5(enValue.toString()); } private static String encryptByMD5(String data) { String re_md5 = new String(); try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(data.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)); } re_md5 = buf.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return re_md5.toUpperCase(); } public static String compare(String jsonStr,String secret ){ JsonParser jsonParser = new JsonParser(); JsonObject jsonObject = jsonParser.parse(jsonStr).getAsJsonObject(); String sign1 = "null"; JsonElement signElement = jsonObject.remove("sign"); if( signElement != null ){ sign1 = signElement.getAsString(); } log.info("sign1: " + sign1); StringBuilder enValue = new StringBuilder(); enValue.append(secret); enValue.append(jsonObject.toString()); enValue.append(secret); String sign2 = encryptByMD5(enValue.toString()); jsonObject.addProperty("sign", sign2); return jsonObject.toString(); } }
這個(gè)工具類在我之前的文章也有但是沒有把這個(gè)方式的放上去,如果有需要用到可直接把一下代碼復(fù)制到這個(gè)Http工具類(見文末) 最后即可直接使用。
/** * 發(fā)送post請求 * @param url 目的url * @param parameters 參數(shù) * @return */ public static String post(String url, Map<String, String> parameters) { String result = "";// 返回的結(jié)果 BufferedReader in = null;// 讀取響應(yīng)輸入流 PrintWriter out = null; StringBuffer sb = new StringBuffer();// 處理請求參數(shù) String params = "";// 編碼之后的參數(shù) try { // 編碼請求參數(shù) if (parameters.size() == 1) { for (String name : parameters.keySet()) { sb.append(name) .append("=") .append(java.net.URLEncoder.encode( parameters.get(name), "UTF-8")); } params = sb.toString(); } else { for (String name : parameters.keySet()) { sb.append(name) .append("=") .append(java.net.URLEncoder.encode( parameters.get(name), "UTF-8")).append("&"); } String temp_params = sb.toString(); params = temp_params.substring(0, temp_params.length() - 1); } // 創(chuàng)建URL對象 java.net.URL connURL = new java.net.URL(url); // 打開URL連接 java.net.HttpURLConnection httpConn = (java.net.HttpURLConnection) connURL .openConnection(); // 設(shè)置通用屬性 httpConn.setRequestProperty("Accept", "*/*"); httpConn.setRequestProperty("Connection", "Keep-Alive"); httpConn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); httpConn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)"); // 設(shè)置POST方式 httpConn.setDoInput(true); httpConn.setDoOutput(true); // 獲取HttpURLConnection對象對應(yīng)的輸出流 out = new PrintWriter(httpConn.getOutputStream()); // 發(fā)送請求參數(shù) out.write(params); // flush輸出流的緩沖 out.flush(); // 定義BufferedReader輸入流來讀取URL的響應(yīng),設(shè)置編碼方式 in = new BufferedReader(new InputStreamReader( httpConn.getInputStream(), "UTF-8")); String line; // 讀取返回的內(nèi)容 while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { e.printStackTrace(); } finally { try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException ex) { ex.printStackTrace(); } } return result; }
在日常開發(fā)中,經(jīng)常會遇到需要記錄IP的需求,接下來分享一個(gè)獲取IP工具類,希望對小伙伴有幫助
package com.cy.xgsm.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetAddress; import java.net.UnknownHostException; import javax.servlet.http.HttpServletRequest; /** * 獲取IP工具類 * @author Dylan * */ public class IpUtils { private static Logger logger = LoggerFactory.getLogger(IpUtils.class); /** * 獲取IP地址 * 使用Nginx等反向代理軟件, 則不能通過request.getRemoteAddr()獲取IP地址 * 如果使用了多級反向代理的話,X-Forwarded-For的值并不止一個(gè),而是一串IP地址,X-Forwarded-For中第一個(gè)非unknown的有效IP字符串,則為真實(shí)IP地址 */ public static String getIpAddr(HttpServletRequest request) { if (request == null) { return "unknown"; } String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Forwarded-For"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Real-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; } public static boolean internalIp(String ip) { byte[] addr = textToNumericFormatV4(ip); return internalIp(addr) || "127.0.0.1".equals(ip); } private static boolean internalIp(byte[] addr) { if (StringUtils.isNull(addr) || addr.length < 2) { return true; } final byte b0 = addr[0]; final byte b1 = addr[1]; final byte section1 = 0x0A; final byte section2 = (byte) 0xAC; final byte section3 = (byte) 0x10; final byte section4 = (byte) 0x1F; final byte section5 = (byte) 0xC0; final byte section6 = (byte) 0xA8; switch (b0) { case section1: return true; case section2: if (b1 >= section3 && b1 <= section4) { return true; } case section5: switch (b1) { case section6: return true; default: return false; } default: return false; } } /** * 將IPv4地址轉(zhuǎn)換成字節(jié) * * @param text IPv4地址 * @return byte 字節(jié) */ public static byte[] textToNumericFormatV4(String text) { if (text.length() == 0) { return null; } byte[] bytes = new byte[4]; String[] elements = text.split("\\.", -1); try { long l; int i; switch (elements.length) { case 1: l = Long.parseLong(elements[0]); if ((l < 0L) || (l > 4294967295L)){ return null; } bytes[0] = (byte) (int) (l >> 24 & 0xFF); bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 2: l = Integer.parseInt(elements[0]); if ((l < 0L) || (l > 255L)){ return null; } bytes[0] = (byte) (int) (l & 0xFF); l = Integer.parseInt(elements[1]); if ((l < 0L) || (l > 16777215L)){ return null; } bytes[1] = (byte) (int) (l >> 16 & 0xFF); bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 3: for (i = 0; i < 2; ++i) { l = Integer.parseInt(elements[i]); if ((l < 0L) || (l > 255L)){ return null; } bytes[i] = (byte) (int) (l & 0xFF); } l = Integer.parseInt(elements[2]); if ((l < 0L) || (l > 65535L)){ return null; } bytes[2] = (byte) (int) (l >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 4: for (i = 0; i < 4; ++i) { l = Integer.parseInt(elements[i]); if ((l < 0L) || (l > 255L)){ return null; } bytes[i] = (byte) (int) (l & 0xFF); } break; default: return null; } } catch (NumberFormatException e) { return null; } return bytes; } public static String getHostIp() { try { return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { } return "127.0.0.1"; } public static String getHostName() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { } return "未知"; } }
感謝各位的閱讀,以上就是“Java怎么提供給第三方使用接口”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Java怎么提供給第三方使用接口這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。