首页 > 编程语言 >Java 提供给第三方使用接口方法

Java 提供给第三方使用接口方法

时间:2022-12-28 10:04:41浏览次数:41  
标签:bitem Java String 积分 接口 return new 第三方

前言

相信有很多小伙伴,在日常的开发中都有遇到过需要调用第三方接口的需求吧,但是自己有没有写过接口提供给第三方使用呢,常规的都是我们调用别人的接口,但是自己需要开发接口提供给第三方使用的场景应该不是很多,很多小伙伴可能会想不就开发一个接口对外开放嘛岂不是很简单,但是在开发接口对外开放,我们需要考虑一个问题,没有限制条件,那岂不是太不安全了,谁都可以调我这个接口了啊。
所以接下来的就是我们需要考虑的问题了,在开发接口的时候就要考虑到安全性的问题,那么应该如何去解决这个问题呢?提供接口给第三方使用的时候需要加上校验保证接口的安全性。
下面是我写的一个例子希望对大家有帮助。

接口Controller

在写接口前一定要签名做签名校验,我的签名方式做了特殊处理,因为接口是对外开放的,这个是为了避免恶意调用接口做的处理,叫做签名的混淆值,这个签名混淆值的作用是就算别人知道了接口,并且知道签名方式也不能被攻击,是为了避免被恶意篡改数据,签名混淆值就是一组特定加密后的数据。

@PostMapping("refundDeductionPoints")
public Result<SysIntegralStatement> refundDeductionPoints (@RequestParam Map<String,String> params){
Result<SysIntegralStatement> result = new Result<SysIntegralStatement>();
try {
//签名校验
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("数字签名无效");
}
if (Common.isEmpty(secret)) {//先签名后幂等校验
return result.setCode(1001).setReason("密钥不能为空");
}
/**
* 幂等校验
* 1.同一个用户操作同一个退货单一分钟内操作该单据视为重复操作(此秘钥已通过特殊处理)
*/
String value = redistempalte.opsForValue().get(secret);
if (Common.isNotEmpty(value)) {
logger.error("重复请求 secret={}",value);
return result.setCode(1007).setReason("重复请求");
}
redistempalte.opsForValue().set(secret, "1",60,TimeUnit.SECONDS);//设置缓存一分钟
return service.refundDeductionPoints(params);
} catch (Exception e) {
logger.error("添加积分流水异常", e);
return result.setCode(ErrorCodes.BUSINESS_ERROR).setReason("生成积分流水失败");
}
}

接口幂等性校验

此接口做幂等性校验,幂等性校验常见解决方案有很多,可以自行根据实际情况选择,
说到幂等首先要先了解什么是幂等
概念:
幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。

在编程中.一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。

这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“getUsername()和setTrue()”函数就是一个幂等函数.

幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次,比如:

订单接口, 不能多次创建订单
支付接口, 重复支付同一笔订单只能扣一次钱
支付宝回调接口, 可能会多次回调, 必须处理重复回调
普通表单提交接口, 因为网络超时等原因多次点击提交, 只能成功一次
等等
解决方案常见的几种方式
唯一索引 – 防止新增脏数据

token机制 – 防止页面重复提交

悲观锁 – 获取数据的时候加锁(锁表或锁行)

乐观锁 – 基于版本号version实现, 在更新数据那一刻校验数据

分布式锁 – redis(jedis、redisson)或zookeeper实现

状态机 – 状态变更, 更新数据时判断状态

如果有小伙伴不理解什么是幂等可以看看官方是解释

实现类ServiceImpl

@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("请求参数不能为空");
}
if (Common.isEmpty(entity.getBitems())) {
return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("请求参数不能为空");
}
int row = 0;
for (ParamIntegral bitem : entity.getBitems()) {
if (Common.isEmpty(bitem.getDdh())) {
return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR).setReason("订单号为必传参数");
}
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() + "没有此订单请联系客服核对信息。");
}
if (orderInfo != null && orderInfo.size() > 1) {
return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR)
.setReason("订单号为" + bitem.getDdh() + "有多个相同订单请联系客服核对信息。");
}
if (!"E".equals(orderInfo.get(0).getDdzt())) {
return new Result<SysIntegralStatement>().setCode(ErrorCodes.INNER_ERROR)
.setReason("订单号为" + bitem.getDdh() + "未确认收货还没产生积分不允许退货。");
}
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() + "积分汇总没有找到此门店,请联系客服核实");
}
BigDecimal kyjf = BigDecimal.ZERO;
if (entity.getReturnGoods() == true) {
// 可用积分小于扣减积分不够扣ERP使用前抵扣
if (bitem.getJfz().compareTo(integral.getKyjf()) == 1) {
kyjf = BigDecimal.ZERO;
} else {
// 可用积分 = 当前可用积分-扣减积分
kyjf = Common.nvl(integral.getKyjf(), BigDecimal.ZERO).subtract(bitem.getJfz());
}
} else {
// 可用积分 = 当前可用积分+退还积分
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());// 消费的积分值
if (bitem.getJfz().compareTo(integral.getKyjf()) == 1) {// 可用积分小于扣减积分不够扣ERP使用前抵扣
statement.setTzhjfz(BigDecimal.ZERO);// 调整后积分值
} else {
statement.setTzhjfz(Common.nvl(integral.getKyjf(), BigDecimal.ZERO).subtract(bitem.getJfz()));// 调整后积分值
}
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()));// 调整后积分值
statement.setXxsm("取消退货(订单号为:" + bitem.getDdh() + "," + "退货单号为:" + entity.getDjh() + ")" + "已退还:"
+ bitem.getJfz() + ":积分");
}
statement.setIntegralId(integral.getIntegralId());// 该门店积分编码
statement.setTzqjfz(integral.getKyjf());// 调整前积分值
statement.setDdh(entity.getDdh());
statement.setCreateTime(new Date());// 流水生成时间
statement.setCreateUser(entity.getUserName());
statement.setCreateUserid(entity.getUserId().intValue());
statement.setJftz("T");// 积分扣减为T
statement.setZt("Y");// 状态 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("操作成功");
}

第三方调用接口Api实现类

模拟第三方合作方调用接口

//此方式以上已写了封装信息就不一一展示了,可以根据实际情况自行操作
private void pushIntegral(Long djlsh) {
FiSjysjsH fiSjysjsh = mapper.selectByPrimaryKey(djlsh);
//订单退货调用某某退货扣减积分接口
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());//时间戳
params.put("timeStamp", timeStamp);
params.put("msgData", msgData);
params.put("msgDigest", SignUtil.sign(msgData+"wf8la1tw7p9o2xz", timeStamp));//生成签名第二个值暂定(wf8la1tw7p9o2xz签名混淆值)
params.put("secret",IDEMPOTENT_SECRET_PREFIX + fiSjysjsh.getDjh() + AppRealm.getCurrentUser().getUserId()+param.getReturnGoods() );//自定义密钥 做幂等校验
String result = HttpCilent.post(B2B_URL, params); //发送http post请求
B2bIntegralResponse res = new Gson().fromJson(result, B2bIntegralResponse.class);
if (null == res) {
throw new RuntimeException("调用积分接口系统异常");
}
if (res.getCode() != 0) {//接口返回失败异常代码提示
throw new RuntimeException("调用积分接口发生异常,异常代码为:"+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();
}



}

HttpCilent工具类

这个工具类在我之前的文章也有但是没有把这个方式的放上去,如果有需要用到可直接把一下代码复制到这个Http工具类 最后即可直接使用。

/**
* 发送post请求
* @param url 目的url
* @param parameters 参数
* @return
*/
public static String post(String url, Map<String, String> parameters) {
String result = "";// 返回的结果
BufferedReader in = null;// 读取响应输入流
PrintWriter out = null;
StringBuffer sb = new StringBuffer();// 处理请求参数
String params = "";// 编码之后的参数
try {
// 编码请求参数
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);
}
// 创建URL对象
java.net.URL connURL = new java.net.URL(url);
// 打开URL连接
java.net.HttpURLConnection httpConn = (java.net.HttpURLConnection) connURL
.openConnection();
// 设置通用属性
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)");
// 设置POST方式
httpConn.setDoInput(true);
httpConn.setDoOutput(true);
// 获取HttpURLConnection对象对应的输出流
out = new PrintWriter(httpConn.getOutputStream());
// 发送请求参数
out.write(params);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应,设置编码方式
in = new BufferedReader(new InputStreamReader(
httpConn.getInputStream(), "UTF-8"));
String line;
// 读取返回的内容
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;
}

标签:bitem,Java,String,积分,接口,return,new,第三方
From: https://blog.51cto.com/u_15733182/5973814

相关文章

  • Java千问06:Java语言中最大的整数再加1等于多少?看完秒懂!
    ​已知Java语言中int类型所能表示的最大整数为2147483647,请问以下代码执行结果是什么?一部分人都会认为这段程序压根就无法通过编译,也有人认为,这段程序能够通过编译,但在运行......
  • Java HashMap原理
    HashMap是Java中用于实现映射关系的一种数据结构。它允许将一个对象(称为键)映射到另一个对象(称为值)。当需要访问值时,可以使用键来查找值。HashMap的实现原理是使用散列函数......
  • Java千问07:你真的会用Java的int型变量吗?有些坑你真的不知道!
    ​我们在进行Java编程的时候,使用最频繁的数据类型基本上就是int型了。平时在使用这种数据类型的过程中,我们似乎也并没有感觉到有什么太多需要注意的地方,其实int类型的变量在......
  • Java千问08:关于Java语言复合赋值运算符的两个问题,快来瞧瞧!
    ​我们知道,在Java以及很多高级编程语言当中,都有一种运算符叫做复合赋值运算符。复合赋值运算符由两个符号组成,它所能完成的运算操作也分为两步:第一步是运算,第二步是赋值。比......
  • JAVA代码审计_若依管理系统
    若依管理系统代码审计该项目使用Mybatis作为数据库持久化框架。全局搜索${、关注xxxmapper.xml文件寻找SQL注入。1)后台角色管理处SQL注入漏洞点“SysRoleMapper.x......
  • windows配置java17
    最近重置系统了,所有东西都得重装一遍。但是为了能继续撸树盖房子,java又是必不可少的,先把java搞回来再说。jdk下载地址https://www.oracle.com/java/technologies/javase......
  • 接口新特性
    接口组成更新接口的组成:常量,抽象方法Java8之前常量:publicstaticfinal抽象方法:publicabstract默认方法(Java8)静态方法(Java8)私有方法(Java9)接口中默认方......
  • Loki Promtail 使用 multiline 对Java 堆栈日志进行多行处理的示例
    promtail使用multiline处理java多行日志文本的示例Environment:promtail_version:v2.7.1originJavalogcontent:logfilecontentstdout-a.log:2022-Dec-270......
  • JavaWeb
    JavaWeb1、基本概念1.1前言静态webhtmlcss动态web技术栈:Servlet/JSP、ASP、php动态web资源开发技术统称为JavaWeb1.2web应用程序:可以提供浏览......
  • Java String类为什么用final修饰
    在Java语言中,使用final修饰符修饰的类是不可以被继承的,也就是说,如果一个类被声明为final,则不能被其他类所继承。因此,当String类被声明为final时,意味着它不能被其他类所继承......