package cn.iocoder.yudao.framework.common.util.http; import cn.hutool.core.codec.Base64; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import jakarta.servlet.http.HttpServletRequest; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Map; /** * HTTP 工具类 * * @author 芋道源码 */ public class HttpUtils { /** * 编码 URL 参数 * * @param value 参数 * @return 编码后的参数 */ public static String encodeUtf8(String value) { return URLEncoder.encode(value, StandardCharsets.UTF_8); } /** * 解码 URL 参数(query parameter) * 注意:此方法会将 + 解码为空格,适用于 query parameter,不适用于 URL path * * @see #decodeUrlPath(String) * @param value 参数 * @return 解码后的参数 */ public static String decodeUtf8(String value) { return URLDecoder.decode(value, StandardCharsets.UTF_8); } /** * 解码 URL 路径 * 与 {@link #decodeUtf8(String)} 不同,此方法不会将 + 解码为空格,保持 + 为字面字符 * 适用于 URL path 部分的解码 * * @param path URL 路径 * @return 解码后的路径 */ public static String decodeUrlPath(String path) { // 先将 + 替换为 %2B,避免被 URLDecoder 解码为空格 String encoded = path.replace("+", "%2B"); return URLDecoder.decode(encoded, StandardCharsets.UTF_8); } public static String replaceUrlQuery(String url, String key, String value) { UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset()); // 先移除;再添加 builder.getQuery().remove(key); builder.addQuery(key, value); return builder.build(); } public static String removeUrlQuery(String url) { if (!StrUtil.contains(url, '?')) { return url; } UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset()); // 移除 query、fragment builder.setQuery(null); builder.setFragment(null); return builder.build(); } /** * 拼接 URL * * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 append 方法 * * @param base 基础 URL * @param query 查询参数 * @param keys query 的 key,对应的原本的 key 的映射。例如说 query 里有个 key 是 xx,实际它的 key 是 extra_xx,则通过 keys 里添加这个映射 * @param fragment URL 的 fragment,即拼接到 # 中 * @return 拼接后的 URL */ public static String append(String base, Map query, Map keys, boolean fragment) { UriComponentsBuilder template = UriComponentsBuilder.newInstance(); UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base); URI redirectUri; try { // assume it's encoded to start with (if it came in over the wire) redirectUri = builder.build(true).toUri(); } catch (Exception e) { // ... but allow client registrations to contain hard-coded non-encoded values redirectUri = builder.build().toUri(); builder = UriComponentsBuilder.fromUri(redirectUri); } template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost()) .userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath()); if (fragment) { StringBuilder values = new StringBuilder(); if (redirectUri.getFragment() != null) { String append = redirectUri.getFragment(); values.append(append); } for (String key : query.keySet()) { if (values.length() > 0) { values.append("&"); } String name = key; if (keys != null && keys.containsKey(key)) { name = keys.get(key); } values.append(name).append("={").append(key).append("}"); } if (values.length() > 0) { template.fragment(values.toString()); } UriComponents encoded = template.build().expand(query).encode(); builder.fragment(encoded.getFragment()); } else { for (String key : query.keySet()) { String name = key; if (keys != null && keys.containsKey(key)) { name = keys.get(key); } template.queryParam(name, "{" + key + "}"); } template.fragment(redirectUri.getFragment()); UriComponents encoded = template.build().expand(query).encode(); builder.query(encoded.getQuery()); } return builder.build().toUriString(); } public static String[] obtainBasicAuthorization(HttpServletRequest request) { String clientId; String clientSecret; // 先从 Header 中获取 String authorization = request.getHeader("Authorization"); authorization = StrUtil.subAfter(authorization, "Basic ", true); if (StringUtils.hasText(authorization)) { authorization = Base64.decodeStr(authorization); clientId = StrUtil.subBefore(authorization, ":", false); clientSecret = StrUtil.subAfter(authorization, ":", false); // 再从 Param 中获取 } else { clientId = request.getParameter("client_id"); clientSecret = request.getParameter("client_secret"); } // 如果两者非空,则返回 if (StrUtil.isNotEmpty(clientId) && StrUtil.isNotEmpty(clientSecret)) { return new String[]{clientId, clientSecret}; } return null; } /** * HTTP post 请求,基于 {@link cn.hutool.http.HttpUtil} 实现 * * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数 * * @param url URL * @param headers 请求头 * @param requestBody 请求体 * @return 请求结果 */ public static String post(String url, Map headers, String requestBody) { try (HttpResponse response = HttpRequest.post(url) .addHeaders(headers) .body(requestBody) .execute()) { return response.body(); } } /** * HTTP get 请求,基于 {@link cn.hutool.http.HttpUtil} 实现 * * 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数 * * @param url URL * @param headers 请求头 * @return 请求结果 */ public static String get(String url, Map headers) { try (HttpResponse response = HttpRequest.get(url) .addHeaders(headers) .execute()) { return response.body(); } } }