一、前言
最近再写调用三方接口传输数据的项目,这篇博客记录项目完成的过程,方便后续再碰到类似的项目可以快速上手
项目结构:
二、编码
这里主要介绍HttpClient发送POST请求工具类和定时器的使用,mvc三层架构编码不做探究
pom.xml
<dependencies>
<!--web启动依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
<!--swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.14</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--测试单元-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application-dev.yml
#####端口配置#####
server:
port: 9991
#####数据源配置#####
spring:
datasource:
username: dev
password: dev1234
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#####mybatis配置#####
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.jzj.pojo
configuration:
map-underscore-to-camel-case: true
#####配置日志文件#####
logging:
config: classpath:logback.xml
#设置日志级别的节点
level:
com:
jzj: debug
Constast
package com.jzj.common;
public class Constast {
/**
* 请求头信息
*/
public static final String CONTENT_TYPE = "application/json;charset=UTF-8";
/**
* 返回状态值
*/
public static final Integer OK = 200;
public static final String YK_URL = "三方接口地址";
}
utils
package com.jzj.utils;
import com.jzj.common.Constast;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* Apache HttpClient发送POST请求工具类
*
* @author 黎明
* @version 1.0
* @date 2023/8/14 14:18
*/
public class HttpUtils {
/**
* 发送post请求
*
* @param url 请求url
* @param jsonParam 请求参数
* @return 响应数据
*/
public static String doPostJson(String url, String jsonParam) {
// 创建一个HttpPost对象,并指定URL
HttpPost httpPost = new HttpPost(url);
// 声明一个CloseableHttpResponse对象来接收请求的响应
CloseableHttpResponse response = null;
// 创建一个CloseableHttpClient对象。wrapClient方法是自定义的方法,用于构建和配置HttpClient对象
CloseableHttpClient httpClient = wrapClient(url);
try {
// 通过重新赋值的方式为HttpPost对象设置URL
httpPost = new HttpPost(url);
// 设置请求头的内容类型。Constast.CONTENT_TYPE表示请求的数据类型
httpPost.setHeader("Content-type", Constast.CONTENT_TYPE);
// 创建一个StringEntity对象,用于封装JSON参数。
StringEntity entity = new StringEntity(jsonParam, "UTF-8");
// 将实体的内容编码设置为与请求头的内容类型相同
entity.setContentEncoding(new BasicHeader("Content-type", Constast.CONTENT_TYPE));
// 将StringEntity对象设置为HttpPost请求的实体
httpPost.setEntity(entity);
// 执行HttpPost请求,并将响应赋值给response对象
response = httpClient.execute(httpPost);
// 判断响应的状态码是否等于200
if (response.getStatusLine().getStatusCode() == Constast.OK) {
// 将响应实体转换为字符串并返回。EntityUtils.toString方法用于读取响应实体的内容。
return EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
}
} catch (Exception e) {
throw new RuntimeException("[发送POST请求错误:]" + e.getMessage());
} finally {
// 释放连接、关闭响应和关闭HttpClient对象
try {
httpPost.releaseConnection();
response.close();
if (httpClient != null) {
httpClient.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 根据URL的协议来配置HttpClient对象
* @param url url地址
* @return CloseableHttpClient
*/
private static CloseableHttpClient wrapClient(String url) {
// 使用HttpClientBuilder类创建一个默认的HttpClient对象
CloseableHttpClient client = HttpClientBuilder.create().build();
if (url.startsWith("https")) { // 检查URL是否以"https"开头,以确定是否需要使用HTTPS协议
// 如果URL以"https"开头,调用方法获取配置了HTTPS支持的CloseableHttpClient对象
client = getCloseableHttpsClients();
}
return client;
}
/**
* 创建一个支持HTTPS的CloseableHttpClient对象
* @return CloseableHttpClient
*/
private static CloseableHttpClient getCloseableHttpsClients() {
// 采用绕过验证的方式处理https请求
SSLClient ssl = new SSLClient();
SSLContext sslcontext = ssl.createIgnoreVerifySSL();
// 设置协议http和https对应的处理socket链接工厂的对象
org.apache.http.config.Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", new SSLConnectionSocketFactory(sslcontext)).build();
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
HttpClients.custom().setConnectionManager(connManager);
// 创建自定义的httpsclient对象
CloseableHttpClient client = HttpClients.custom().setConnectionManager(connManager).build();
return client;
}
}
package com.jzj.utils;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
/**
* 用于创建一个支持绕过HTTPS验证的SSLContext对象
*
* @author 黎明
* @version 1.0
* @date 2023/8/14 14:35
*/
public class SSLClient {
// 使用@SuppressWarnings注解来抑制未使用的警告
@SuppressWarnings("unused")
public SSLContext createIgnoreVerifySSL() {
// 创建套接字对象
SSLContext sslContext = null;
try {
// 指定TLS版本
sslContext = SSLContext.getInstance("TLSv1.2");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("[创建套接字失败:] " + e.getMessage());
}
// 实现X509TrustManager接口,用于绕过验证
X509TrustManager trustManager = new X509TrustManager() {
// 该方法用于验证客户端证书
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
String paramString) throws CertificateException {
}
// 该方法用于验证服务器证书
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
String paramString) throws CertificateException {
}
// 该方法返回受信任的颁发机构(证书颁发机构)数组。在这里,返回null表示不对颁发机构进行限制
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
};
try {
// 初始化sslContext对象
sslContext.init(null, new TrustManager[]{trustManager}, null);
} catch (KeyManagementException e) {
throw new RuntimeException("[初始化套接字失败:] " + e.getMessage());
}
return sslContext;
}
}
scheduled
package com.jzj.scheduled;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jzj.common.Constast;
import com.jzj.pojo.TrsfToYk;
import com.jzj.pojo.TrsfToYkLog;
import com.jzj.service.SfSfmxService;
import com.jzj.service.TrsfToYkLogService;
import com.jzj.service.TrsfToYkService;
import com.jzj.utils.HttpUtils;
import com.jzj.vo.YkResultVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 定时器任务
*
* @author 黎明
* @version 1.0
* @date 2023/8/15 15:03
*/
@Component
@Slf4j
public class TrsfToYkScheduled {
// 注入trsfToYkService
@Autowired
private TrsfToYkService trsfToYkService;
// 注入trsfToYkLogService
@Autowired
private TrsfToYkLogService trsfToYkLogService;
// 注入SfSfmxService
@Autowired
private SfSfmxService sfSfmxService;
/**
* 审方信息下传英克
*/
@Scheduled(cron = "*/10 * * * * *")
public void toYk() {
// 根据视图查询所有bs=0审方信息
List<TrsfToYk> sfAllInfo = trsfToYkService.findAll();
if (sfAllInfo.size() != 0) { // 判断是否有数据
ObjectMapper mapper = new ObjectMapper();
String requestData = null;
try {
requestData = mapper.writeValueAsString(sfAllInfo);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
log.info("发送的数据是:{}", requestData);
String responseData = HttpUtils.doPostJson(Constast.YK_URL, requestData);
log.info("响应的数据是:{}", responseData);
JSONObject responseJson = JSONObject.parseObject(responseData);
YkResultVo ykResultVo = responseJson.toJavaObject(YkResultVo.class);
// 判断响应状态是否为200
if (ykResultVo.getStatus().equals("200") && ykResultVo.getStatus() != null) {
// 记录日志
ykResultVo.getData().stream().forEach(v -> {
TrsfToYkLog trsfToYkLog = new TrsfToYkLog();
trsfToYkLog.setAuditId(v.getAuditId());
trsfToYkLog.setStatus(v.getStatus());
trsfToYkLogService.insertLog(trsfToYkLog);
});
// 更新审方明细表bs字段
ykResultVo.getData().stream().filter(v -> v.getStatus().equals("200")).forEach(v -> {
long aid = Long.parseLong(v.getAuditId());
sfSfmxService.renewalBs(aid);
});
}
}
}
}
三、总结
该定时任务每10秒执行一次,将满足条件的审方信息发送到三方系统,并根据返回的结果进行相应的日志记录和数据更新操作。再调用三方接口时,使用的是封装好了的工具类将post请求发送给三方接口,并对https安全传输协议做了跳过操作。