提交 384c02e1 authored 作者: wangmenglong's avatar wangmenglong

增加爬虫

上级 f561fd6b
...@@ -205,6 +205,20 @@ ...@@ -205,6 +205,20 @@
<artifactId>jsoup</artifactId> <artifactId>jsoup</artifactId>
<version>1.17.2</version> <version>1.17.2</version>
</dependency> </dependency>
<!-- Selenium 核心依赖(包含 WebDriver 所有核心类) -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.18.1</version> <!-- 最新稳定版,适配 Chrome 120+ -->
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.4</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
......
...@@ -2,20 +2,41 @@ package com.jfb.recruit.controller.api; ...@@ -2,20 +2,41 @@ package com.jfb.recruit.controller.api;
import base.controller.BaseController; import base.controller.BaseController;
import base.result.BaseResult; import base.result.BaseResult;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.ParseException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Random;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
/** /**
* @author wangmenglong * @author wangmenglong
...@@ -47,19 +68,34 @@ public class ApiJsonpController extends BaseController { ...@@ -47,19 +68,34 @@ public class ApiJsonpController extends BaseController {
* @return: com.github.pagehelper.PageInfo * @return: com.github.pagehelper.PageInfo
**/ **/
@GetMapping("/run") @GetMapping("/run")
public BaseResult run(HttpServletRequest req){ public BaseResult run(HttpServletRequest req, @RequestBody JSONObject jsonObject){
// 1. 目标爬取网址(静态页面,无反爬) // 1. 目标爬取网址(静态页面,无反爬)
String targetUrl = req.getParameter("url"); String targetUrl = jsonObject.getString("url");
String cookie = jsonObject.getString("cookie");
try { try {
// 2. 模拟浏览器发送请求(设置User-Agent避免被识别为爬虫) // 2. 模拟浏览器发送请求(设置User-Agent避免被识别为爬虫)
Document document = Jsoup.connect(targetUrl) Document document = Jsoup.connect(targetUrl)
.userAgent(getRandomUserAgent()) // 随机User-Agent .userAgent(getRandomUserAgent()) // 随机User-Agent
.referrer("https://www.baidu.com") // 模拟从百度跳转(Referer) .referrer("https://www.baidu.com") // 模拟从百度跳转(Referer)
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") // 接受的内容类型 .header("Host", "xian.baixing.com")
.header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8") // 语言偏好 .header("Accept", "application/json, text/javascript, image/webp, */*; q=0.01")
.header("Cache-Control", "max-age=0") // 缓存控制 .header("Accept-Encoding", "gzip, deflate, br")
.header("Accept-Language", "zh-CN,zh;q=0.9")
.header("Cookie",cookie)
.header("If-Modified-Since", "Fri, 09 Jan 2026 02:20:30 GMT")
.header("Priority", "u=1,i")
.header("Referer", "https://xian.baixing.com/gongren/m37318/%E5%8B%9E%E8%B5%84%E5%80%9D%E5%8B%9E%E8%B5%84%E5%81%9D=&query=")
.header("Sec-Ch-Ua", "\"Google Chrome\";v=\"143\", \"Chromium\";v=\"143\", \"Not A Brand\";v=\"24\"")
.header("Sec-Ch-Ua-Mobile", "?0")
.header("Sec-Ch-Ua-Platform", "\"Windows\"")
.header("Sec-Fetch-Dest", "empty")
.header("Sec-Fetch-Mode", "cors")
.header("Sec-Fetch-Site", "same-origin")
.header("Sec-Fetch-User", "?1")
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36")
.header("X-Requested-With", "XMLHttpRequest")
.timeout(10000) // 超时时间10秒 .timeout(10000) // 超时时间10秒
.get(); // 发送GET请求 .get(); // 发送GET请求
...@@ -83,4 +119,184 @@ public class ApiJsonpController extends BaseController { ...@@ -83,4 +119,184 @@ public class ApiJsonpController extends BaseController {
} }
/**
* @description: 获取openId
* @author: wangmenglong
* @date; 2023/12/7 10:07
* @param: [req]
* @return: com.github.pagehelper.PageInfo
**/
@GetMapping("/run2")
public BaseResult run2(HttpServletRequest req, @RequestBody JSONObject jsonObject){
// 1. 目标爬取网址(静态页面,无反爬)
String targetUrl = jsonObject.getString("url");
String cookie = jsonObject.getString("cookie");
String property = jsonObject.getString("property");
String userToken = jsonObject.getString("userToken");
// 2. 创建HttpClient实例
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 3. 构造HttpGet请求
HttpGet httpGet = new HttpGet(targetUrl);
// 4. 设置模拟浏览器的请求头(核心:添加Cookie)
// ========== 关键:添加Cookie ==========
httpGet.addHeader(new BasicHeader("Cookie", cookie));
httpGet.addHeader(new BasicHeader("Property", property));
httpGet.addHeader(new BasicHeader("User-Token", userToken));
// 其他核心请求头(和之前一致)
httpGet.addHeader(new BasicHeader("Host", "www.baidu.com"));
httpGet.addHeader(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"));
httpGet.addHeader(new BasicHeader("Accept", "application/json, text/plain, */*"));
httpGet.addHeader(new BasicHeader("Referer", "https://we.51job.com/pc/search?jobArea=170300&keyword=%E6%99%AE%E5%B7%A5&searchType=2&keywordType=guess_exp_tag6"));
// 5. 执行请求(可选:添加爬取间隔,避免反爬)
Thread.sleep(3000); // 3秒间隔
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
// 6. 处理响应
int statusCode = response.getStatusLine().getStatusCode();
System.out.println("响应状态码:" + statusCode);
if (statusCode != 200) {
System.out.println("请求失败,状态码:" + statusCode);
}
// 解析响应内容
HttpEntity entity = response.getEntity();
if (entity != null) {
String responseContent = EntityUtils.toString(entity);
System.out.println("响应内容片段:\n" + responseContent.substring(0, 500));
}
} catch (ParseException e) {
System.out.println("响应解析失败:" + e.getMessage());
}
} catch (IOException | InterruptedException e) {
System.out.println("请求异常:" + e.getMessage());
}
return BaseResult.success();
}
/**
* @description: 获取openId
* @author: wangmenglong
* @date; 2023/12/7 10:07
* @param: [req]
* @return: com.github.pagehelper.PageInfo
**/
@GetMapping("/run3")
public BaseResult run3(HttpServletRequest req, @RequestBody JSONObject jsonObject){
System.setProperty("webdriver.chrome.driver", "D:\\chromedriver-win64\\chromedriver.exe");
// 1. 配置 Chrome 浏览器选项(核心:反爬伪装)
ChromeOptions options = new ChromeOptions();
// 🔴 核心反爬:禁用自动化检测(避免被网站识别为爬虫)
options.addArguments("--disable-blink-features=AutomationControlled");
// 🔴 无头模式(无界面运行,节省资源)
options.addArguments("--headless=new");
// 🔴 伪装真实 UA(从浏览器复制)
options.addArguments("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36");
// 🔴 可选:禁用图片/视频加载(提升爬取速度)
options.addArguments("--blink-settings=imagesEnabled=false");
// 🔴 可选:配置代理IP(避免IP封禁,替换为你的代理)
// options.addArguments("--proxy-server=http://123.45.67.89:8080");
// 2. 初始化 WebDriver(模拟浏览器)
WebDriver driver = new ChromeDriver(options);
// 设置隐式等待(等待页面元素加载)
driver.manage().timeouts().implicitlyWait(10000L, TimeUnit.MILLISECONDS);
// 设置页面加载超时
driver.manage().timeouts().pageLoadTimeout(15000L, TimeUnit.MILLISECONDS);
try {
// 3. 模拟人类访问行为(随机延迟+逐步操作)
Random random = new Random();
// 访问拉勾网首页
driver.get("https://we.51job.com/pc/search?jobArea=170300&keyword=%E6%99%AE%E5%B7%A5&searchType=2&keywordType=guess_exp_tag6");
// 模拟人类等待(2-5秒随机延迟)
Thread.sleep(2000 + random.nextInt(3000));
// 🔴 等待搜索框加载完成(显式等待,比隐式更可靠)
WebDriverWait wait = new WebDriverWait(driver, 8000);
WebElement searchInput = wait.until(
ExpectedConditions.presenceOfElementLocated(By.cssSelector("input[placeholder='请输入职位关键词']"))
);
// 模拟人类输入(逐字输入,而非一次性输入)
String keyword = "普工";
for (char c : keyword.toCharArray()) {
searchInput.sendKeys(String.valueOf(c));
Thread.sleep(100 + random.nextInt(200)); // 每个字符间隔100-300ms
}
// 模拟点击搜索按钮
WebElement searchBtn = driver.findElement(By.cssSelector("button.search_button"));
searchBtn.click();
Thread.sleep(3000 + random.nextInt(2000)); // 等待搜索结果加载
// 4. 解析爬取结果(示例:获取职位列表)
WebElement jobList = driver.findElement(By.className("job-list"));
System.out.println("===== 爬取到的职位列表 =====");
System.out.println(jobList.getText());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
System.out.println("爬取异常:" + e.getMessage());
} finally {
// 5. 释放资源(必须quit(),而非close(),避免驱动残留)
driver.quit();
}
return BaseResult.success();
}
/**
* @description: 获取openId
* @author: wangmenglong
* @date; 2023/12/7 10:07
* @param: [req]
* @return: com.github.pagehelper.PageInfo
**/
@GetMapping("/run4")
public BaseResult run4(HttpServletRequest req, @RequestBody JSONObject jsonObject)throws Exception{
Map<String, String> headers = new HashMap<String, String>();
headers.put("Cookie","RECOMMEND_TIP=true; user_trace_token=20230509172245-850b8329-0db6-49d5-8ee5-788463473366; LGUID=20230509172245-ee291504-af55-4823-8b8f-da7830adea64; _ga=GA1.2.1941570256.1683624167; index_location_city=%E5%85%A8%E5%9B%BD; _gid=GA1.2.744431736.1684134362; privacyPolicyPopup=false; __lg_stoken__=00ef87c190275da025cc19a93d14d5da80c4c3ff29516c88d738dd7350f8601ae184994af7785dc2260517aa65b80ae0048d5bdb5ea64e76bf2b4df769b1de46bfa3cc6bd487; SEARCH_ID=c6e9d66fa6f64d48874952a58bf47660; gate_login_token=v1####da9e29af0db73d825a22a9a882bf9ddbb316eae052443e729b53cab3f19a8e70; LG_HAS_LOGIN=1; hasDeliver=0; __SAFETY_CLOSE_TIME__26120270=1; JSESSIONID=ABAAABAABEIABCI02041966E510E7120309F7B2F34013BF; WEBTJ-ID=20230515193746-1881f33c337109-00aed6e8f8a02b-7b515477-1327104-1881f33c33814dd; _putrc=743692222AE66441123F89F2B170EADC; login=true; unick=%E7%94%A8%E6%88%B77560; showExpriedIndex=1; showExpriedCompanyHome=1; showExpriedMyPublish=1; Hm_lvt_4233e74dff0ae5bd0a3d81c6ccf756e6=1683624167,1684134362,1684150667; sensorsdata2015session=%7B%7D; X_HTTP_TOKEN=d5afe4428dfdf76486605148610ad9240e30d415e3; Hm_lpvt_4233e74dff0ae5bd0a3d81c6ccf756e6=1684150668; TG-TRACK-CODE=index_zhaopin; LGRID=20230515193751-230d34f5-3f31-4ddd-8bb0-23d58400b756; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2226120270%22%2C%22first_id%22%3A%22187ffd2094fb06-0d35cf63f3901a-7b515477-1327104-187ffd20950ca3%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E8%87%AA%E7%84%B6%E6%90%9C%E7%B4%A2%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC%22%2C%22%24latest_referrer%22%3A%22https%3A%2F%2Fcn.bing.com%2F%22%2C%22%24os%22%3A%22Windows%22%2C%22%24browser%22%3A%22Chrome%22%2C%22%24browser_version%22%3A%22113.0.0.0%22%7D%2C%22%24device_id%22%3A%22187ffd2094fb06-0d35cf63f3901a-7b515477-1327104-187ffd20950ca3%22%7D");
headers.put("Connection","keep-alive");
headers.put("Accept", "");
headers.put("Accept-Language","zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
headers.put("User-Agent","Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N)"
+"ppleWebKit/537.36 (KHTML, like Gecko) "
+"Chrome/113.0.0.0 Mobile Safari/537.36 Edg/113.0.1774.42");
headers.put("Content-Type", "text/html; charset=utf-8");
headers.put("Referer","https://www.lagou.com/jobs/list_%E5%A4%A7%E6%95%B0%E6%8D%AE/p-city_0?&cl=false&fromSearch=true&labelWords=&suginput=");
headers.put("Origin", "https://www.lagou.com");
headers.put("X-Requested-With", "XMLHttpRequest");
headers.put("X-Anit-Forge-Token", "None");
headers.put("Cache-Control","no-cache");
headers.put("X-Anit-Forge-Code","0");
headers.put("Host","www.lagou.com");
Map<String, String> params = new HashMap<String, String>();
params.put("kd", "普工");
params.put("city", "全国");
for (int i = 1; i < 31; i++) {
params.put("pn", String.valueOf(i));
}
for (int i = 1; i < 31; i++){
params.put("pn", String.valueOf(i));
HttpClientResp result = HttpClientUtils.doPost("https://www.lagou.com/jobs/positionAjax.json?"+ "needAddtionalResult=false&first=true&px=default",headers,params);
System.out.println((result.getContent()));
HttpClientHdfsUtils.createFileBySysTime("hdfs://master:9820","page"+i,result.toString());
Thread.sleep(1 * 500);
}
return BaseResult.success();
}
} }
package com.jfb.recruit.controller.api;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
public class HttpClientHdfsUtils {
/**
* 根据系统当前时间在 HDFS 上创建文件并写入数据。
* 会以当前日期作为文件夹名,在 /JobData 目录下创建文件夹,然后在该文件夹中创建指定文件名的文件并写入数据。
*
* @param url HDFS 的 URI,例如 "hdfs://localhost:9000"
* @param fileName 要创建的文件名
* @param data 要写入文件的数据
*/
public static void createFileBySysTime(String url, String fileName, String data) {
// 指定操作 HDFS 的用户为 root
System.setProperty("HADOOP_USER_NAME", "root");
// 定义文件路径变量
Path filePath = null;
// 获取系统当前时间
Calendar calendar = Calendar.getInstance();
Date currentTime = calendar.getTime();
// 定义日期格式化对象,将日期格式化为 yyyyMMdd 的形式
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
// 获取格式化后的日期字符串,作为文件夹名
String directoryName = dateFormat.format(currentTime);
// 创建 Hadoop 配置对象
Configuration conf = new Configuration();
// 将传入的 URL 转换为 URI 对象
URI hdfsUri = URI.create(url);
// 使用 try-with-resources 语句自动管理 FileSystem 资源,确保资源正确关闭
try (FileSystem fileSystem = FileSystem.get(hdfsUri, conf)) {
// 构建要创建的目录路径
filePath = new Path("/SESSECS/" + directoryName);
// 检查目录是否存在,如果不存在则创建
if (!fileSystem.exists(filePath)) {
// 创建目录,包括所有必要的父目录
fileSystem.mkdirs(filePath);
}
// 构建要创建的文件的完整路径
Path fullFilePath = new Path(filePath.toString() + "/" + fileName);
// 在指定路径创建文件并获取输出流
try (FSDataOutputStream outputStream = fileSystem.create(fullFilePath)) {
// 将数据转换为字节数组输入流
ByteArrayInputStream inputStream = new ByteArrayInputStream(data.getBytes());
// 将输入流中的数据复制到输出流中,即写入文件
IOUtils.copyBytes(inputStream, outputStream, conf, true);
}
} catch (IOException e) {
// 打印异常堆栈信息,方便调试
System.err.println("在 HDFS 上创建文件并写入数据时发生错误: " + e.getMessage());
e.printStackTrace();
}
}
}
package com.jfb.recruit.controller.api;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* HttpClientResp 类用于封装 HTTP 响应的相关信息,包括响应状态码、响应内容和请求头信息。
* 该类实现了 Serializable 接口,以便可以进行序列化操作。
*/
public class HttpClientResp implements Serializable {
// 序列化版本号,用于保证序列化和反序列化的兼容性
private static final long serialVersionUID = -2224539827395038194L;
// 响应状态码
private int code;
// 响应数据
private String content;
// 请求头信息,使用 Map 存储,键为头信息的名称,值为头信息的值
private Map<String, String> headers;
/**
* 空参构造函数,用于创建一个空的 HttpClientResp 对象。
*/
public HttpClientResp() {
this.headers = new HashMap<>();
}
/**
* 构造函数,用于创建一个仅包含响应状态码的 HttpClientResp 对象。
*
* @param code 响应状态码
*/
public HttpClientResp(int code) {
this.code = code;
this.headers = new HashMap<>();
}
/**
* 构造函数,用于创建一个仅包含响应内容的 HttpClientResp 对象。
*
* @param content 响应内容
*/
public HttpClientResp(String content) {
this.content = content;
this.headers = new HashMap<>();
}
/**
* 构造函数,用于创建一个包含响应状态码和响应内容的 HttpClientResp 对象。
*
* @param code 响应状态码
* @param content 响应内容
*/
public HttpClientResp(int code, String content) {
this.code = code;
this.content = content;
this.headers = new HashMap<>();
}
/**
* 构造函数,用于创建一个包含响应状态码、响应内容和请求头信息的 HttpClientResp 对象。
*
* @param code 响应状态码
* @param content 响应内容
* @param headers 请求头信息
*/
public HttpClientResp(int code, String content, Map<String, String> headers) {
this.code = code;
this.content = content;
this.headers = headers != null ? headers : new HashMap<>();
}
/**
* 获取响应状态码。
*
* @return 响应状态码
*/
public int getCode() {
return code;
}
/**
* 设置响应状态码。
*
* @param code 响应状态码
*/
public void setCode(int code) {
this.code = code;
}
/**
* 获取响应内容。
*
* @return 响应内容
*/
public String getContent() {
return content;
}
/**
* 设置响应内容。
*
* @param content 响应内容
*/
public void setContent(String content) {
this.content = content;
}
/**
* 获取请求头信息。
*
* @return 请求头信息的 Map
*/
public Map<String, String> getHeaders() {
return headers;
}
/**
* 设置请求头信息。
*
* @param headers 请求头信息的 Map
*/
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
/**
* 向请求头信息中添加一个键值对。
*
* @param key 请求头的名称
* @param value 请求头的值
*/
public void addHeader(String key, String value) {
this.headers.put(key, value);
}
/**
* 获取响应状态码,与 getCode 方法功能相同,提供一个更具描述性的方法名。
*
* @return 响应状态码
*/
public int getStatusCode() {
return code;
}
/**
* 将请求头信息保存到指定的 JSON 文件中。
*
* @param filePath 要保存的 JSON 文件的路径
* @throws IOException 如果保存文件时发生 I/O 错误
*/
public void saveHeadersToJson(String filePath) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValue(new File(filePath), headers);
}
/**
* 重写 toString 方法,用于方便打印对象的信息。
*
* @return 包含响应状态码和响应内容的字符串
*/
@Override
public String toString() {
return "HttpClientResp [code=" + code + ", content=" + content + ", headers=" + headers + "]";
}
/**
* 内部类,用于模拟 ObjectMapper 类,用于将请求头信息保存到 JSON 文件中。
*/
private static class ObjectMapper {
public void writeValue(File file, Map<String, String> headers) {
throw new UnsupportedOperationException("Not implemented");
}
}
}
package com.jfb.recruit.controller.api;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
/**
* HttpClientUtils 类提供了使用 Apache HttpClient 进行 HTTP POST 请求的工具方法。
* 包含封装请求头、请求参数,执行请求并获取响应内容,以及释放资源等功能。
*/
public class HttpClientUtils {
// 编码格式,发送编码格式统一用 UTF-8
private static final String ENCODING = "UTF-8";
// 设置连接超时时间,单位毫秒
private static final int CONNECT_TIMEOUT = 6000;
// 请求获取数据的超时时间(即响应时间),单位毫秒
private static final int SOCKET_TIMEOUT = 6000;
/**
* 封装 HTTP 请求头。
* 将传入的请求头参数添加到 HttpRequestBase 对象中。
*
* @param params 请求头参数的键值对映射
* @param httpMethod HttpRequestBase 对象,用于表示 HTTP 请求
*/
public static void packageHeader(Map<String, String> params, HttpRequestBase httpMethod) {
// 检查请求头参数是否为空
if (params != null) {
// 获取请求头参数的键值对集合
Set<Entry<String, String>> entrySet = params.entrySet();
// 遍历键值对集合,将每个键值对添加到请求头中
for (Entry<String, String> entry : entrySet) {
httpMethod.setHeader(entry.getKey(), entry.getValue());
}
}
}
/**
* 封装 HTTP 请求参数。
* 将传入的请求参数封装到 HttpEntityEnclosingRequestBase 对象中。
*
* @param params 请求参数的键值对映射
* @param httpMethod HttpEntityEnclosingRequestBase 对象,用于表示包含实体的 HTTP 请求
* @throws UnsupportedEncodingException 如果不支持指定的编码格式
*/
public static void packageParam(Map<String, String> params, HttpEntityEnclosingRequestBase httpMethod) throws UnsupportedEncodingException {
// 检查请求参数是否为空
if (params != null) {
// 创建一个 NameValuePair 列表,用于存储请求参数
List<NameValuePair> nvps = new ArrayList<>();
// 获取请求参数的键值对集合
Set<Entry<String, String>> entrySet = params.entrySet();
// 遍历键值对集合,将每个键值对添加到 NameValuePair 列表中
for (Entry<String, String> entry : entrySet) {
nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
// 将 NameValuePair 列表封装到 UrlEncodedFormEntity 中,并设置编码格式
httpMethod.setEntity(new UrlEncodedFormEntity(nvps, ENCODING));
}
}
/**
* 获取 HTTP 响应内容。
* 执行 HTTP 请求并返回响应结果的封装对象。
*
* @param httpResponse CloseableHttpResponse 对象,用于表示 HTTP 响应
* @param httpClient CloseableHttpClient 对象,用于执行 HTTP 请求
* @param httpMethod HttpRequestBase 对象,用于表示 HTTP 请求
* @return HttpClientResp 对象,封装了响应状态码和响应内容
* @throws Exception 如果执行请求或处理响应时发生异常
*/
public static HttpClientResp getHttpClientResult(CloseableHttpResponse httpResponse, CloseableHttpClient httpClient, HttpRequestBase httpMethod) throws Exception {
// 执行 HTTP 请求
httpResponse = httpClient.execute(httpMethod);
// 检查响应是否有效
if (httpResponse != null && httpResponse.getStatusLine() != null) {
String content = "";
// 检查响应实体是否存在
if (httpResponse.getEntity() != null) {
// 将响应实体内容转换为字符串,并设置编码格式
content = EntityUtils.toString(httpResponse.getEntity(), ENCODING);
}
// 创建并返回 HttpClientResp 对象,封装响应状态码和响应内容
return new HttpClientResp(httpResponse.getStatusLine().getStatusCode(), content);
}
// 如果响应无效,返回表示内部服务器错误的 HttpClientResp 对象
return new HttpClientResp(HttpStatus.SC_INTERNAL_SERVER_ERROR);
}
/**
* 通过 HttpClient Post 方式提交请求头和请求参数,从服务端返回状态码和 JSON 数据内容。
*
* @param url 请求的 URL
* @param headers 请求头参数的键值对映射
* @param params 请求参数的键值对映射
* @return HttpClientResp 对象,封装了响应状态码和响应内容
* @throws Exception 如果执行请求或处理响应时发生异常
*/
public static HttpClientResp doPost(String url, Map<String, String> headers, Map<String, String> params) throws Exception {
// 创建默认的 CloseableHttpClient 对象
CloseableHttpClient httpClient = HttpClients.createDefault();
// 创建 HttpPost 对象,用于表示 POST 请求
HttpPost httpPost = new HttpPost(url);
// 封装请求配置项,设置连接超时时间和响应超时时间
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
// 设置 POST 请求的配置项
httpPost.setConfig(requestConfig);
// 封装请求头
packageHeader(headers, httpPost);
// 封装请求参数
packageParam(params, httpPost);
// 初始化 CloseableHttpResponse 对象,用于存储响应结果
CloseableHttpResponse httpResponse = null;
try {
// 执行请求并获得响应结果
return getHttpClientResult(httpResponse, httpClient, httpPost);
} finally {
// 释放资源
release(httpResponse, httpClient);
}
}
/**
* 释放 httpclient(HTTP 请求)对象资源和 httpResponse(HTTP 响应)对象资源。
*
* @param httpResponse CloseableHttpResponse 对象,用于表示 HTTP 响应
* @param httpClient CloseableHttpClient 对象,用于执行 HTTP 请求
* @throws IOException 如果关闭资源时发生 I/O 异常
*/
private static void release(CloseableHttpResponse httpResponse, CloseableHttpClient httpClient) throws IOException {
// 检查响应对象是否为空,若不为空则关闭
if (httpResponse != null) {
httpResponse.close();
}
// 检查客户端对象是否为空,若不为空则关闭
if (httpClient != null) {
httpClient.close();
}
}
}
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论