HttpClient工具类
2021/11/30 23:38:35
本文主要是介绍HttpClient工具类,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
package com.jw.ctds.utils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; 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.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContextBuilder; import org.apache.http.conn.ssl.TrustStrategy; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.servlet.http.HttpServletRequest; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; import java.util.*; import java.util.Map.Entry; public class HttpUtils { static Logger log = LoggerFactory.getLogger(HttpUtils.class); /** * http post请求传参的方法 返回String */ public static String httpPostWithParams(String url, Map<String, String> params) { try { return httpPostWithParams(url, null, params); } catch (Exception e) { throw new RuntimeException(e); } } /** * http post请求传参的方法 返回String */ public static String httpPostWithParams(String url, Map<String, String> headers, Map<String, String> params) throws Exception { List<BasicNameValuePair> formparams = new ArrayList<BasicNameValuePair>(); if (null != params) { for (Entry<String, String> param : params.entrySet()) { formparams.add(new BasicNameValuePair(param.getKey(), param.getValue())); } } return httpPostWithParams(url, headers, formparams); } /** * http post请求传参的方法 返回String */ public static String httpPostWithParams(String url, Map<String, String> headers, List<BasicNameValuePair> formParams) throws Exception { String result = ""; // 创建默认的httpClient实例. CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; // 创建httppost HttpPost httpPost = new HttpPost(url); try { if (null != headers) { for (Entry<String, String> header : headers.entrySet()) { httpPost.setHeader(header.getKey(), header.getValue()); } } if (null != formParams) { UrlEncodedFormEntity uefEntity = new UrlEncodedFormEntity(formParams, "UTF-8"); httpPost.setEntity(uefEntity); } response = httpClient.execute(httpPost); result = IOUtils.toString(response.getEntity().getContent(), "UTF-8"); } catch (Exception e) { } finally { IOUtils.closeQuietly(response); } return result; } /** * http post请求传参的方法 返回实体 */ public static CloseableHttpResponse httpPostWithParam(String url, Map<String, String> headers, Map<String, String> params) throws Exception { List<BasicNameValuePair> formparams = new ArrayList<BasicNameValuePair>(); if (null != params) { for (Entry<String, String> param : params.entrySet()) { formparams.add(new BasicNameValuePair(param.getKey(), param.getValue())); } } return httpPostWithParam(url, headers, formparams); } /** * http post请求传参的方法 返回实体 */ public static CloseableHttpResponse httpPostWithParam(String url, Map<String, String> headers, List<BasicNameValuePair> formParams) throws Exception { // 创建默认的httpClient实例. CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; // 创建httppost HttpPost httpPost = new HttpPost(url); try { if (null != headers) { for (Entry<String, String> header : headers.entrySet()) { httpPost.setHeader(header.getKey(), header.getValue()); } } if (null != formParams) { UrlEncodedFormEntity uefEntity = new UrlEncodedFormEntity(formParams, "UTF-8"); httpPost.setEntity(uefEntity); } response = httpClient.execute(httpPost); } catch (Exception e) { log.error("remote post exception:" + e.getMessage()); } return response; } /** * http post请求传参的方法参数是Json字符串 返回String */ public static String httpPostWithJson(String url, Map<String, String> headers, Map<String, String> params) throws Exception { return httpPostWithJson(url, headers, JSON.toJSONString(params)); } /** * http post请求传参的方法参数是Json字符串 返回String */ public static String httpPostWithJson(String url, Map<String, String> headers, String jsonString) throws Exception { String result = ""; // 创建默认的httpClient实例. CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; // 创建httppost HttpPost httpPost = new HttpPost(url); try { if (null != headers) { for (Entry<String, String> header : headers.entrySet()) { httpPost.setHeader(header.getKey(), header.getValue()); } } if (null != jsonString) { StringEntity se = new StringEntity(jsonString, "UTF-8"); httpPost.setEntity(se); } response = httpClient.execute(httpPost); result = IOUtils.toString(response.getEntity().getContent(), "UTF-8"); } catch (Exception e) { log.error("remote post exception:" + e.getMessage()); } finally { IOUtils.closeQuietly(response); } return result; } public static HttpRequestData create(String url) { return new HttpRequestData(url); } public static String sendJsonWithHttp(String surl, String json) throws Exception { URL url = new URL(surl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestProperty("Content-Type", "application/json;charset=utf-8"); conn.setRequestMethod("POST");// 提交模式 conn.setRequestProperty("Content-Length", json.getBytes().length + ""); conn.setConnectTimeout(100000);// 连接超时单位毫秒 // conn.setReadTimeout(200000);// 读取超时 单位毫秒 conn.setDoOutput(true);// 是否输入参数 conn.setDoInput(true); conn.setUseCaches(false); conn.connect(); DataOutputStream out = new DataOutputStream(conn.getOutputStream()); out.write(json.getBytes()); out.flush(); out.close(); BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); StringBuffer sb = new StringBuffer(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } reader.close(); conn.disconnect(); return sb.toString(); } /** * get 请求 * * @param url * @return */ public static String httpGetWithParam(String url, Map<String, Object> map,Map<String, String> headers) { String result = ""; CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; HttpEntity entity = null; if (map != null) { Set<String> set = map.keySet(); StringBuffer sBuffer = new StringBuffer(); sBuffer.append(url + "?"); for (String key : set) { sBuffer.append(key + "=" + map.get(key) + "&"); } url = sBuffer.toString(); url = url.substring(0, url.length() - 1); } HttpGet method = new HttpGet(url); try { if (null != headers) { for (Entry<String, String> header : headers.entrySet()) { method.setHeader(header.getKey(), header.getValue()); } } response = httpClient.execute(method); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { entity = response.getEntity(); result = EntityUtils.toString(entity); } } catch (Exception ex) { throw new RuntimeException(ex); } return result; } // 子类,构造 create().setHeader().addParameters.post() 用法 public static class HttpRequestData { private String _url = ""; private Map<String, String> _header = new HashMap<String, String>(); private List<BasicNameValuePair> _params = new ArrayList<BasicNameValuePair>(); private HttpRequestData(String url) { _url = url; } public HttpRequestData setHeader(String name, String value) { _header.put(name, value); return this; } public HttpRequestData addParameters(String name, String value) { _params.add(new BasicNameValuePair(name, value)); return this; } public String post() throws Exception { String result = httpPostWithParams(_url, _header, _params); clean(); return result; } private void clean() throws Exception { _url = ""; _header.clear(); _params.clear(); } } public static String getIP(HttpServletRequest request) { String ip = ""; if (request.getHeader("x-forwarded-for") == null) { ip = request.getRemoteAddr(); } else { ip = request.getHeader("x-forwarded-for"); } return ip; } public static String getUserAgent(HttpServletRequest request) { return request.getHeader("User-Agent"); } public static void main(String[] args) { } /** * 读取request流 * * @return * @author guoyx */ public static String readReqStr(HttpServletRequest request) { BufferedReader reader = null; StringBuilder sb = new StringBuilder(); try { reader = new BufferedReader(new InputStreamReader(request.getInputStream(), "utf-8")); String line = null; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (null != reader) { reader.close(); } } catch (IOException e) { } } return sb.toString(); } /** * 包含附件的post请求 * * @param urlStr * 请求地址 * @param textMap * 文本参数 * @param fileMap * 文件参数 * @return * @author majie * @date 2017年8月25日 下午4:24:55 */ @SuppressWarnings("rawtypes") public static String doPostOfFile(String urlStr, Map<String, String> textMap, Map<String, String> fileMap) { String res = ""; HttpURLConnection conn = null; String BOUNDARY = "-----------------12345654321-----------"; try { URL url = new URL(urlStr); trustAllHttpsCertificates(); HttpsURLConnection.setDefaultHostnameVerifier(hv); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setReadTimeout(30000); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)"); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); conn.setRequestProperty("Charset", "UTF-8"); OutputStream out = new DataOutputStream(conn.getOutputStream()); if (textMap != null) { StringBuffer strBuf = new StringBuffer(); Iterator iter = textMap.entrySet().iterator(); while (iter.hasNext()) { Entry entry = (Entry) iter.next(); String inputName = (String) entry.getKey(); String inputValue = (String) entry.getValue(); if (inputValue == null) { continue; } strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n"); strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n"); strBuf.append(inputValue); } out.write(strBuf.toString().getBytes()); } if (fileMap != null) { Iterator iter = fileMap.entrySet().iterator(); while (iter.hasNext()) { Entry entry = (Entry) iter.next(); String inputName = (String) entry.getKey(); String inputValue = (String) entry.getValue(); if (inputValue == null) { continue; } File file = new File(inputValue); String filename = file.getName(); StringBuffer strBuf = new StringBuffer(); strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n"); strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"; filename=\"" + filename + "\"\r\n"); strBuf.append("Content-Type:application/octet-stream\r\n\r\n"); out.write(strBuf.toString().getBytes()); DataInputStream in = new DataInputStream(new FileInputStream(file)); int bytes = 0; byte[] bufferOut = new byte[1024]; while ((bytes = in.read(bufferOut)) != -1) { out.write(bufferOut, 0, bytes); } in.close(); } } byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes(); out.write(endData); out.flush(); out.close(); int responseCode = conn.getResponseCode(); if (responseCode == 200) { // 读取返回数据 StringBuffer strBuf = new StringBuffer(); BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); String line = null; while ((line = reader.readLine()) != null) { strBuf.append(line).append("\n"); } res = strBuf.toString(); reader.close(); reader = null; } else { StringBuffer error = new StringBuffer(); BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(conn.getErrorStream(), "UTF-8")); String line1 = null; while ((line1 = bufferedReader.readLine()) != null) { error.append(line1).append("\n"); } res = error.toString(); bufferedReader.close(); bufferedReader = null; } } catch (Exception e) { System.out.println("发送POST请求出错。" + e); e.printStackTrace(); } finally { if (conn != null) { conn.disconnect(); conn = null; } } return res; } private static void trustAllHttpsCertificates() throws Exception { javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1]; javax.net.ssl.TrustManager tm = new miTM(); trustAllCerts[0] = tm; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, null); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); } static class miTM implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager { public X509Certificate[] getAcceptedIssuers() { return null; } public boolean isServerTrusted(X509Certificate[] certs) { return true; } public boolean isClientTrusted(X509Certificate[] certs) { return true; } public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException { return; } public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException { return; } } static HostnameVerifier hv = new HostnameVerifier() { public boolean verify(String urlHostName, SSLSession session) { System.out.println("Warning: URL Host: " + urlHostName + " vs. " + session.getPeerHost()); return true; } }; /** * 向指定 URL 发送POST方法的请求 * * @param url * 发送请求的 URL * @param param * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return 所代表远程资源的响应结果 */ public static String sendPost(String url, String param) { log.info(url + "----发送 POST----"+param); PrintWriter out = null; BufferedReader in = null; String result = ""; try { URL realUrl = new URL(url); // 打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); // 设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); conn.setRequestProperty("Content-Type", "application/json; charset=utf-8"); // 发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); // 获取URLConnection对象对应的输出流 out = new PrintWriter(conn.getOutputStream()); // 发送请求参数 out.print(param); // flush输出流的缓冲 out.flush(); // 定义BufferedReader输入流来读取URL的响应 in = new BufferedReader( new InputStreamReader(conn.getInputStream())); String line; while ((line = in.readLine()) != null) { result += line; } log.info("result---------------"+result); } catch (Exception e) { log.info("发送 POST 请求出现异常!"+e); e.printStackTrace(); } //使用finally块来关闭输出流、输入流 finally{ try{ if(out!=null){ out.close(); } if(in!=null){ in.close(); } } catch(IOException ex){ ex.printStackTrace(); } } return result; } /** * 读取request转换JSONObject * @param request * @return */ public static JSONObject getParameterMapFromRequest(HttpServletRequest request) { JSONObject json = new JSONObject(); Map map = request.getParameterMap(); if (map != null) { Set set = map.entrySet(); Iterator iterator = set.iterator(); while (iterator.hasNext()) { Entry entry = (Entry) iterator.next(); if (entry.getValue() instanceof String[]) { String[] values = (String[]) entry.getValue(); json.put((String) entry.getKey(), values[0]); } } } return json; } //通过body传JSON格式参数 public static String doHttpRequest(String url, JSONObject jsonObject) throws Exception{ //设置传入参数 StringEntity stringEntity = new StringEntity(jsonObject.toJSONString(),"UTF-8"); stringEntity.setContentEncoding("UTF-8"); stringEntity.setContentType("application/json"); HttpPost httpPost = new HttpPost(url); httpPost.setHeader("Connection","close"); httpPost.setEntity(stringEntity); CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); HttpResponse resp = closeableHttpClient.execute(httpPost); HttpEntity he = resp.getEntity(); String respContent = EntityUtils.toString(he, "UTF-8"); return respContent; // return (JSONObject) JSONObject.parse(respContent); } /** * 用 GET 方法提交数据 * * @param url - 请求地址 * @param paramMap - 请求参数 - Map集合 * @param headerParamMap * @return * @throws * @throws ClientProtocolException * @throws IOException */ public static JSONObject get(String url, Map<String, Object> paramMap, Map<String, String> headerParamMap) { JSONObject jsonObject = null; try { CloseableHttpResponse execute; CloseableHttpClient client; if (paramMap != null && paramMap.size() > 0) { url = formatGetParameter(url, paramMap); } HttpGet get = new HttpGet(url); if (headerParamMap != null && headerParamMap.size() > 0) { for (String key : headerParamMap.keySet()) { get.addHeader(key, headerParamMap.get(key)); } } client = HttpClients.custom() .setDefaultRequestConfig( RequestConfig.custom() .setSocketTimeout(60000) .setConnectTimeout(60000) .setConnectionRequestTimeout(60000) .build()).build(); if (url.startsWith("https")) { client = createSSLClientDefault(); } execute = client.execute(get); String content = EntityUtils.toString(execute.getEntity(), "UTF-8"); jsonObject = JSONObject.parseObject(content); Charset charset = ContentType.getOrDefault(execute.getEntity()).getCharset(); try { execute.close(); client.close(); } catch (Exception e) { } } catch (Exception e) { log.error("异常:",e); return null; } return jsonObject; } public static JSONObject postMethodUrl(String url, String body, Map<String, String> headerParamMap) { return postMethodUrl(url, body, null, headerParamMap, null); } /** * 用POST方式提交数据 * * @param url 请求地址 * @param body * @param paramMap 请求参数 - Map集合 * @param headerParamMap * @return JSONObject * @throws */ public static JSONObject postMethodUrl(String url, String body, Map<String, Object> paramMap, Map<String, String> headerParamMap, CloseableHttpClient closeableHttpClient) { JSONObject jsonObject = null; try { CloseableHttpResponse execute; CloseableHttpClient client; // System.getProperties().put("proxySet", "true"); // System.getProperties().put("proxyHost", "10.168.96.111"); // System.getProperties().put("proxyPort", "1888"); HttpPost post = new HttpPost(url); post.addHeader("Content-Type","application/json"); if (headerParamMap != null && headerParamMap.size() > 0) { for (String key : headerParamMap.keySet()) { post.addHeader(key, headerParamMap.get(key)); } } if (paramMap != null && paramMap.size() > 0) { List<NameValuePair> nvps = new ArrayList<>(); for (String key : paramMap.keySet()) { Object value = paramMap.get(key); if (value != null) { nvps.add(new BasicNameValuePair(key, String.valueOf(value))); } } post.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8")); } if (body != null && !"".equals(body)) { post.setEntity(new StringEntity(body, "UTF-8")); } if (closeableHttpClient == null) { client = HttpClients.custom() .setUserAgent("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0") .setDefaultRequestConfig( RequestConfig.custom() .setSocketTimeout(40000) .setConnectTimeout(40000) .setConnectionRequestTimeout(40000) .build()).build(); if (url.startsWith("https")) { client = createSSLClientDefault(); } } else { client = closeableHttpClient; } execute = client.execute(post); String content = EntityUtils.toString(execute.getEntity()); if (StringUtils.isNotBlank(content)) { jsonObject = JSONObject.parseObject(content); } Charset charset = ContentType.getOrDefault(execute.getEntity()).getCharset(); execute.close(); client.close(); // System.getProperties().put("proxySet", "false"); } catch (Exception e) { log.error("异常:",e); return null; } return jsonObject; } /** * 格式化GET参数 * * @param url 请求地址 * @param argsMap 请求参数 * @return * @throws */ public static String formatGetParameter(String url, Map<String, Object> argsMap) { if (url != null && url.length() > 0) { if (!url.endsWith("?")) { url = url + "?"; } if (argsMap != null && !argsMap.isEmpty()) { Set<Entry<String, Object>> entrySet = argsMap.entrySet(); Iterator<Entry<String, Object>> iterator = entrySet.iterator(); while (iterator.hasNext()) { Entry<String, Object> entry = iterator.next(); if (entry != null) { String key = entry.getKey(); String value = (String) entry.getValue(); url = url + key + "=" + value; if (iterator.hasNext()) { url = url + "&"; } } } } } return url; } public static CloseableHttpClient createSSLClientDefault() { try { SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { // 信任所有 public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { return true; } }).build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext); return HttpClients .custom() .setSSLSocketFactory(sslsf) .setDefaultRequestConfig(RequestConfig.custom() .setSocketTimeout(10000) .setConnectTimeout(10000) .setConnectionRequestTimeout(10000) .build()) .build(); } catch (KeyManagementException e) { log.error("异常:",e); } catch (NoSuchAlgorithmException e) { log.error("异常:",e); } catch (KeyStoreException e) { log.error("异常:",e); } return null; } public static JSONObject postMethodUrlJson(String url, String body, CloseableHttpClient closeableHttpClient) { JSONObject jsonObject = null; try { CloseableHttpResponse execute; CloseableHttpClient client; // System.getProperties().put("proxySet", "true"); // System.getProperties().put("proxyHost", "10.168.96.111"); // System.getProperties().put("proxyPort", "1888"); HttpPost post = new HttpPost(url); post.addHeader("Content-Type", "application/json"); if (body != null && !"".equals(body)) { post.setEntity(new StringEntity(body, "UTF-8")); } if (closeableHttpClient == null) { client = HttpClients.custom() .setUserAgent("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0") .setDefaultRequestConfig( RequestConfig.custom() .setSocketTimeout(60000) .setConnectTimeout(60000) .setConnectionRequestTimeout(60000) .build()).build(); if (url.startsWith("https")) { client = createSSLClientDefault(); } } else { client = closeableHttpClient; } execute = client.execute(post); String content = EntityUtils.toString(execute.getEntity()); if (StringUtils.isNotBlank(content)) { jsonObject = JSONObject.parseObject(content); } execute.close(); client.close(); // System.getProperties().put("proxySet", "false"); } catch (Exception e) { log.error("异常:",e); return null; } return jsonObject; } }
这篇关于HttpClient工具类的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2025-01-08CCPM如何缩短项目周期并降低风险?
- 2025-01-08Omnivore 替代品 Readeck 安装与使用教程
- 2025-01-07Cursor 收费太贵?3分钟教你接入超低价 DeepSeek-V3,代码质量逼近 Claude 3.5
- 2025-01-06PingCAP 连续两年入选 Gartner 云数据库管理系统魔力象限“荣誉提及”
- 2025-01-05Easysearch 可搜索快照功能,看这篇就够了
- 2025-01-04BOT+EPC模式在基础设施项目中的应用与优势
- 2025-01-03用LangChain构建会检索和搜索的智能聊天机器人指南
- 2025-01-03图像文字理解,OCR、大模型还是多模态模型?PalliGema2在QLoRA技术上的微调与应用
- 2025-01-03混合搜索:用LanceDB实现语义和关键词结合的搜索技术(应用于实际项目)
- 2025-01-03停止思考数据管道,开始构建数据平台:介绍Analytics Engineering Framework